Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,6 @@ dmypy.json
# Cython debug symbols
cython_debug/


tests/example.db
tests/example2.db
19 changes: 10 additions & 9 deletions dbml_sqlite/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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).

Expand All @@ -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.

Expand Down Expand Up @@ -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.

Expand All @@ -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.

Expand All @@ -161,19 +161,19 @@ 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))
if join:
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.

Expand All @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -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 <weele.me@gmail.com>"]
repository = 'https://github.com/dvanderweele/DBML_SQLite'
Expand All @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Binary file modified tests/example.db
Binary file not shown.
Binary file modified tests/example2.db
Binary file not shown.
70 changes: 47 additions & 23 deletions tests/test_dbml_sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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'
Expand All @@ -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'

Expand All @@ -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'))
Expand All @@ -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"
Expand Down