diff --git a/.gitignore b/.gitignore index 351933d..e019647 100644 --- a/.gitignore +++ b/.gitignore @@ -145,3 +145,6 @@ dmypy.json # Cython debug symbols cython_debug/ + +tests/example.db +tests/example2.db diff --git a/dbml_sqlite/core.py b/dbml_sqlite/core.py index 9dd5a83..0161bb7 100644 --- a/dbml_sqlite/core.py +++ b/dbml_sqlite/core.py @@ -2,7 +2,7 @@ import os import uuid from pydbml import PyDBML -from pydbml.classes import Enum +from pydbml.classes import Table, Enum, Reference from pathlib import Path from itertools import chain @@ -43,7 +43,7 @@ def toSQLite(dbml=".", emulation="full", tableExists=True, indexExists=True, joi results = "".join(results) return results -def validDBMLFile(s): +def validDBMLFile(s: str): """ Return a boolean indicating whether passed string has valid `.dbml` file extension. Case-sensitive (i.e. `.DBML` not accepted). @@ -58,7 +58,7 @@ def validDBMLFile(s): else: return False -def processFile(target, emulationMode, tableExists=True, indexExists=True, idxNameFunc=uuid.uuid4, join=True): +def processFile(target: Path, emulationMode: str, tableExists=True, indexExists=True, idxNameFunc=uuid.uuid4, join=True): """ Given a target `.dbml` file, parse and generate a valid SQLite string. @@ -119,7 +119,7 @@ def processIndex(table, index, idxNameFunc=uuid.uuid4, indexExists=True, join=Tr parts = "".join(parts) return parts -def processEnum(enum, tableExists=True, join=True): +def processEnum(enum: Enum, tableExists=True, join=True): """ Take an Enum object generated by the PyDBML library and use it to generate SQLite DDL for creating an enum table for "full" enum emulation mode only. @@ -139,7 +139,7 @@ def processEnum(enum, tableExists=True, join=True): segments = "".join(segments) return segments -def processTable(table, emulationMode, tableExists=True, join=True): +def processTable(table: Table, emulationMode, tableExists=True, join=True): """ Generate SQLite DDL for creating a table. @@ -161,11 +161,11 @@ def processTable(table, emulationMode, tableExists=True, join=True): segments.append(processColumn(col, emulationMode, False)) if i < len(table.columns) - 1: segments.append(',\n') - for j, ref in enumerate(table.refs): + for j, ref in enumerate(table.get_refs()): if j == 0: segments.append(',\n') segments.append(processRef(ref, False)) - if j < len(table.refs) - 1: + if j < len(table.get_refs()) - 1: segments.append(',\n') segments.append('\n);\n') segments = list(chain.from_iterable(segments)) @@ -173,7 +173,7 @@ def processTable(table, emulationMode, tableExists=True, join=True): segments = "".join(segments) return segments -def processRef(ref, join=True): +def processRef(ref: Reference, join=True): """ Convert a Ref object parsed by PyDBML from dbml into SQLite DDL. @@ -186,7 +186,8 @@ def processRef(ref, join=True): """ segments = [] segments.append(' FOREIGN KEY(') - segments.append(f'{ref.col.name}) REFERENCES {ref.ref_table.name}({ref.ref_col.name})') + segments.append(f'{ref.col1[0].name}) REFERENCES {ref.col2[0].table.name}({ref.col2[0].name})') + # segments.append(f'{ref.col.name}) REFERENCES {ref.ref_table.name}({ref.ref_col.name})') if ref.on_update: segments.append(f' ON UPDATE {ref.on_update.upper()}') if ref.on_delete: diff --git a/pyproject.toml b/pyproject.toml index 89c4a85..13ac28d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "DBML_SQLite" -version = "0.3.3" +version = "0.3.4" description = "A package that provides a CLI tool and a functional API for converting dbml files to SQLite DDL." authors = ["Dave VanderWeele "] repository = 'https://github.com/dvanderweele/DBML_SQLite' @@ -11,7 +11,7 @@ readme = 'README.md' [tool.poetry.dependencies] python = "^3.7" -pydbml = "^0.3.4" +pydbml = "^1.2.0" click = "^8.0.0" [tool.poetry.dev-dependencies] diff --git a/requirements.txt b/requirements.txt index d86e17e..6b08b2f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -20,7 +20,7 @@ lessqlite==0.1.3 msgpack==1.0.2 parso==0.8.2 Pillow==8.2.0 -pydbml==0.3.4 +pydbml==1.2.0 Pygments==2.9.0 pynvim==0.4.3 pyparsing==2.4.7 diff --git a/tests/example.db b/tests/example.db index aacda2c..f39930e 100644 Binary files a/tests/example.db and b/tests/example.db differ diff --git a/tests/example2.db b/tests/example2.db index 9dc504b..70e6e95 100644 Binary files a/tests/example2.db and b/tests/example2.db differ diff --git a/tests/test_dbml_sqlite.py b/tests/test_dbml_sqlite.py index 5d57123..2c387df 100644 --- a/tests/test_dbml_sqlite.py +++ b/tests/test_dbml_sqlite.py @@ -16,25 +16,34 @@ class MockItem: def __init__(self, name): self.name = name class MockColumn: - def __init__(self, name, Type, pk, not_null, unique, default): + def __init__(self, name, Type, pk, not_null, unique, default, table, autoinc=False): self.name = name self.type = Type self.pk = pk self.not_null = not_null self.unique = unique self.default = default + self.table = table + self.autoinc = autoinc class MockRef: - def __init__(self, col, ref_table, ref_col, on_update, on_delete): - self.col = col - self.ref_table = ref_table - self.ref_col = ref_col + def __init__(self, database, type, col1, col2, name, comment, on_update, on_delete, _inline): + self.database = database + self.type = type + self.col1 = col1 + self.col2 = col2 + self.name = name + self.comment = comment self.on_update = on_update self.on_delete = on_delete + self._inline = _inline class MockTable: def __init__(self, name, columns, refs): self.name = name self.columns = columns self.refs = refs + + def get_refs(self): + return self.refs class MockIndex: def __init__(self, name, unique, subjects, table): self.name = name @@ -79,15 +88,15 @@ def test_coercion(): def test_process_column(): # name, Type, pk, not_null, unique, default - c1 = MockColumn("c1", 1, None, None, None, None) - c2 = MockColumn("c2", 'INTEGER', True, False, False, None) - c3 = MockColumn("c3", 'REAL', False, True, True, None) - c4 = MockColumn('c4', 'TEXT', False, False, False, 'howdy') + c1 = MockColumn("c1", 1, None, None, None, None, None) + c2 = MockColumn("c2", 'INTEGER', True, False, False, None, None) + c3 = MockColumn("c3", 'REAL', False, True, True, None, None) + c4 = MockColumn('c4', 'TEXT', False, False, False, 'howdy', None) i1 = MockItem('i1') i2 = MockItem('i2') e1 = MockEnum('e1', [i1, i2]) - c5 = MockColumn('c5', e1, False, True, False, None) - c6 = MockColumn('c6', 'REAL', False, True, False, 12.345) + c5 = MockColumn('c5', e1, False, True, False, None, None) + c6 = MockColumn('c6', 'REAL', False, True, False, 12.345, None) with pytest.raises(TypeError): processColumn(c1, 'full') assert processColumn(c2, 'full') == ' c2 INTEGER PRIMARY KEY' @@ -98,10 +107,16 @@ def test_process_column(): assert processColumn(c6, 'full') == ' c6 REAL NOT NULL DEFAULT 12.345' def test_process_ref(): - fc = MockColumn('foreign_key', None, None, None, None, None) + fc = MockColumn('foreign_key', None, None, None, None, None, None) t = MockTable('foreign_table', [fc], []) - lc = MockColumn('local_key', None, None, None, None, None) - r = MockRef(lc, t, fc, 'NO ACTION', 'NO ACTION') + lc = MockColumn('local_key', None, None, None, None, None, None) + fc.table = t + + # def __init__(self, database, type, col1, col2, name, comment, on_update, on_delete, _inline): + # r = MockRef(lc, t, fc, 'NO ACTION', 'NO ACTION') + + r = MockRef(None, None, [lc], [fc], None, None, 'NO ACTION', 'NO ACTION', None) + o = processRef(r) assert o == ' FOREIGN KEY(local_key) REFERENCES foreign_table(foreign_key) ON UPDATE NO ACTION ON DELETE NO ACTION' @@ -111,18 +126,27 @@ def test_process_table(): 1 ) multiple refs 2 ) joined output """ - lc1 = MockColumn('l1', 'INTEGER', None, None, None, None) - lc2 = MockColumn('l2', 'INTEGER', None, None, None, None) - fc1 = MockColumn('f1', 'INTEGER', None, None, None, None) - fc2 = MockColumn('f2', 'INTEGER', None, None, None, None) + lc1 = MockColumn('l1', 'INTEGER', None, None, None, None, None) + lc2 = MockColumn('l2', 'INTEGER', None, None, None, None, None) + fc1 = MockColumn('f1', 'INTEGER', None, None, None, None, None) + fc2 = MockColumn('f2', 'INTEGER', None, None, None, None, None) fortab = MockTable('ft', [fc1, fc2], []) - r1 = MockRef(lc1, fortab, fc1, 'NO ACTION', 'NO ACTION') - r2 = MockRef(lc2, fortab, fc2, 'NO ACTION', 'NO ACTION') + + fc1.table = fortab + fc2.table = fortab + + # def __init__(self, database, type, col1, col2, name, comment, on_update, on_delete, _inline): + # r1 = MockRef(lc1, fortab, fc1, 'NO ACTION', 'NO ACTION') + # r2 = MockRef(lc2, fortab, fc2, 'NO ACTION', 'NO ACTION') + + r1 = MockRef(None, None, [lc1], [fc1], None, None, 'NO ACTION', 'NO ACTION', None) + r2 = MockRef(None, None, [lc2], [fc2], None, None, 'NO ACTION', 'NO ACTION', None) + loctab = MockTable('lt', [lc1, lc2], [r1, r2]) o = processTable(loctab, 'full', False, True) assert o == "CREATE TABLE lt (\n l1 INTEGER,\n l2 INTEGER,\n FOREIGN KEY(l1) REFERENCES ft(f1) ON UPDATE NO ACTION ON DELETE NO ACTION,\n FOREIGN KEY(l2) REFERENCES ft(f2) ON UPDATE NO ACTION ON DELETE NO ACTION\n);\n" -def test_process_enum(): + items = [] items.append(MockItem('Joe')) items.append(MockItem('Bob')) @@ -138,8 +162,8 @@ def test_process_file(): def test_process_index(): mytab = MockTable('mytab', [], []) - col1 = MockColumn('col1', None, None, None, None, None) - col2 = MockColumn('col2', None, None, None, None, None) + col1 = MockColumn('col1', None, None, None, None, None, None) + col2 = MockColumn('col2', None, None, None, None, None, None) idx = MockIndex('myidx', False, [col1, col2], mytab) o = processIndex(mytab, idx, MockNameFunc, False, True) assert o == "CREATE INDEX myidx ON mytab (col1, col2);\n"