Skip to content

Commit 559fee0

Browse files
authored
Remove creation_ddl from model (google#42)
This really belongs in the update code, so move it from model.Model to admin.update.CreateTable. Also because creating the table fresh on a new database also requires involve adding secondary indices, add a helper method that takes a model and returns the list of ddl necessary to set up the table for that model.
1 parent 4cdc8aa commit 559fee0

6 files changed

Lines changed: 101 additions & 76 deletions

File tree

spanner_orm/admin/update.py

Lines changed: 60 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,27 @@ class CreateTable(SchemaUpdate):
4545

4646
def __init__(self, model):
4747
self._model = model
48-
self._existing_model = metadata.SpannerMetadata.model(model.table)
4948

5049
def ddl(self):
51-
return self._model.creation_ddl
50+
fields = [
51+
'{} {}'.format(name, field.ddl())
52+
for name, field in self._model.schema.items()
53+
]
54+
index_ddl = 'PRIMARY KEY ({})'.format(', '.join(self._model.primary_keys))
55+
statement = 'CREATE TABLE {} ({}) {}'.format(self._model.table,
56+
', '.join(fields), index_ddl)
57+
58+
if self._model.interleaved:
59+
statement += ', INTERLEAVE IN PARENT {parent} ON CASCADE DELETE'.format(
60+
parent=self._model.interleaved.table)
61+
return statement
5262

5363
def validate(self):
5464
if not self._model.table:
5565
raise error.SpannerError('New table has no name')
5666

57-
if self._existing_model:
67+
existing_model = metadata.SpannerMetadata.model(self._model.table)
68+
if existing_model:
5869
raise error.SpannerError('Table {} already exists'.format(
5970
self._model.table))
6071

@@ -71,7 +82,7 @@ def _validate_parent(self):
7182
self._model.table, self._model.interleaved.table)
7283
for parent_key, key in zip(parent_primary_keys, primary_keys):
7384
if parent_key != key:
74-
raise error.SpnnerError(message)
85+
raise error.SpannerError(message)
7586
if len(parent_primary_keys) > len(primary_keys):
7687
raise error.SpannerError(message)
7788

@@ -92,25 +103,25 @@ class DropTable(SchemaUpdate):
92103

93104
def __init__(self, table_name):
94105
self._table = table_name
95-
self._existing_model = metadata.SpannerMetadata.model(table_name)
96106

97107
def ddl(self):
98108
return 'DROP TABLE {}'.format(self._table)
99109

100110
def validate(self):
101-
if not self._existing_model:
111+
existing_model = metadata.SpannerMetadata.model(self._table)
112+
if not existing_model:
102113
raise error.SpannerError('Table {} does not exist'.format(self._table))
103114

104115
# Model indexes include the primary index
105-
if len(self._existing_model.indexes) > 1:
116+
if len(existing_model.indexes) > 1:
106117
raise error.SpannerError('Table {} has a secondary index'.format(
107118
self._table))
108119

109120
self._validate_not_interleaved()
110121

111122
def _validate_not_interleaved(self):
112123
for model in metadata.SpannerMetadata.models().values():
113-
if model.interleaved == self._existing_model:
124+
if model.interleaved == existing_model:
114125
raise error.SpannerError('Table {} has interleaved table {}'.format(
115126
self._table, model.table))
116127
for index in model.indexes.values():
@@ -129,14 +140,14 @@ def __init__(self, table_name, column_name, field):
129140
self._table = table_name
130141
self._column = column_name
131142
self._field = field
132-
self._model = metadata.SpannerMetadata.model(table_name)
133143

134144
def ddl(self):
135145
return 'ALTER TABLE {} ADD COLUMN {} {}'.format(self._table, self._column,
136146
self._field.ddl())
137147

138148
def validate(self):
139-
if not self._model:
149+
model = metadata.SpannerMetadata.model(self._table)
150+
if not model:
140151
raise error.SpannerError('Table {} does not exist'.format(self._table))
141152
if not self._field.nullable():
142153
raise error.SpannerError('Column {} is not nullable'.format(self._column))
@@ -151,16 +162,16 @@ class DropColumn(SchemaUpdate):
151162
def __init__(self, table_name, column_name):
152163
self._table = table_name
153164
self._column = column_name
154-
self._model = metadata.SpannerMetadata.model(table_name)
155165

156166
def ddl(self):
157167
return 'ALTER TABLE {} DROP COLUMN {}'.format(self._table, self._column)
158168

159169
def validate(self):
160-
if not self._model:
170+
model = metadata.SpannerMetadata.model(self._table)
171+
if not model:
161172
raise error.SpannerError('Table {} does not exist'.format(self._table))
162173

163-
if self._column not in self._model.schema:
174+
if self._column not in model.schema:
164175
raise error.SpannerError('Column {} does not exist on {}'.format(
165176
self._column, self._table))
166177

@@ -182,25 +193,25 @@ def __init__(self, table_name, column_name, field):
182193
self._table = table_name
183194
self._column = column_name
184195
self._field = field
185-
self._model = metadata.SpannerMetadata.model(table_name)
186196

187197
def ddl(self):
188198
return 'ALTER TABLE {} ALTER COLUMN {} {}'.format(self._table, self._column,
189199
self._field.ddl())
190200

191201
def validate(self):
192-
if not self._model:
202+
model = metadata.SpannerMetadata.model(self._table)
203+
if not model:
193204
raise error.SpannerError('Table {} does not exist'.format(self._table))
194205

195-
if self._column not in self._model.schema:
206+
if self._column not in model.schema:
196207
raise error.SpannerError('Column {} does not exist on {}'.format(
197208
self._column, self._table))
198209

199-
if self._column in self._model.primary_keys:
210+
if self._column in model.primary_keys:
200211
raise error.SpannerError('Column {} is a primary key on {}'.format(
201212
self._column, self._table))
202213

203-
old_field = self._model.schema[self._column]
214+
old_field = model.schema[self._column]
204215
# Validate that the only alteration is to change column nullability
205216
if self._field.field_type() != old_field.field_type():
206217
raise error.SpannerError('Column {} is changing type'.format(
@@ -223,7 +234,6 @@ def __init__(self,
223234
self._columns = columns
224235
self._parent_table = interleaved
225236
self._storing_columns = storing_columns or []
226-
self._model = metadata.SpannerMetadata.model(table_name)
227237

228238
def ddl(self):
229239
statement = 'CREATE INDEX {} ON {} ({})'.format(self._index, self._table,
@@ -235,40 +245,42 @@ def ddl(self):
235245
return statement
236246

237247
def validate(self):
238-
if not self._model:
248+
model = metadata.SpannerMetadata.model(self._table)
249+
if not model:
239250
raise error.SpannerError('Table {} does not exist'.format(self._table))
240251

241252
if not self._columns:
242253
raise error.SpannerError('Index {} has no columns'.format(self._index))
243254

244-
if self._index in self._model.indexes:
255+
if self._index in model.indexes:
245256
raise error.SpannerError('Index {} already exists'.format(self._index))
246257

247-
self._validate_columns()
258+
self._validate_columns(model)
248259

249260
if self._parent_table:
250-
self._validate_parent()
261+
self._validate_parent(model)
251262

252-
def _validate_columns(self):
263+
def _validate_columns(self, model):
253264
for column in self._columns:
254-
if not column in self._model.columns:
265+
if not column in model.columns:
255266
raise error.SpannerError('Table {} has no column {}'.format(
256267
self._table, column))
257268

258269
for column in self._storing_columns:
259-
if not column in self._model.columns:
270+
if not column in model.columns:
260271
raise error.SpannerError('Table {} has no column {}'.format(
261272
self._table, column))
262-
if column in self._model.primary_keys:
273+
if column in model.primary_keys:
263274
raise error.SpannerError('{} is part of the primary key for {}'.format(
264275
column, self._table))
265276

266-
def validate_parent(self):
267-
parent = self._model.interleaved
277+
def validate_parent(self, model):
278+
parent = model.interleaved
268279
while parent:
269280
if parent == self._parent_table:
270281
break
271282
parent = parent.interleaved
283+
272284
if not parent:
273285
raise error.SpannerError('{} is not a parent of table {}'.format(
274286
self._parent_table, self._table))
@@ -280,13 +292,13 @@ class DropIndex(SchemaUpdate):
280292
def __init__(self, table_name, index_name):
281293
self._table = table_name
282294
self._index = index_name
283-
self._model = metadata.SpannerMetadata.model(table_name)
284295

285296
def ddl(self):
286297
return 'DROP INDEX {}'.format(self._index)
287298

288299
def validate(self):
289-
if not self._model:
300+
model = metadata.SpannerMetadata.model(self._table)
301+
if not model:
290302
raise error.SpannerError('Table {} does not exist'.format(self._table))
291303

292304
db_index = self._model.indexes.get(self._index)
@@ -308,3 +320,20 @@ def execute(self):
308320

309321
def validate(self):
310322
pass
323+
324+
325+
def model_creation_ddl(model):
326+
ddl_list = [CreateTable(model).ddl()]
327+
328+
for model_index in model.indexes:
329+
if model_index.primary_key:
330+
continue
331+
create_index = CreateIndex(
332+
model.table,
333+
model_index.name,
334+
model_index.columns,
335+
interleaved=model_index.parent,
336+
storing_columns=model_index.storing_columns)
337+
ddl_list.append(create_index.ddl())
338+
339+
return ddl_list

spanner_orm/model.py

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -138,22 +138,6 @@ def __getattr__(cls, name):
138138
return cls.indexes[name]
139139
raise AttributeError(name)
140140

141-
@property
142-
def creation_ddl(cls):
143-
fields = [
144-
'{} {}'.format(name, field.ddl()) for name, field in cls.schema.items()
145-
]
146-
field_ddl = '({})'.format(', '.join(fields))
147-
index_ddl = 'PRIMARY KEY ({})'.format(', '.join(cls.primary_keys))
148-
trailing_statements = [index_ddl]
149-
if cls.interleaved:
150-
interleave_ddl = 'INTERLEAVE IN PARENT {parent} ON CASCADE DELETE'.format(
151-
parent=cls.interleaved.table)
152-
trailing_statements.append(interleave_ddl)
153-
trailing_ddl = ', '.join(trailing_statements)
154-
return 'CREATE TABLE {table_name} {fields} {trailing}'.format(
155-
table_name=cls.table, fields=field_ddl, trailing=trailing_ddl)
156-
157141
@property
158142
def column_prefix(cls):
159143
return cls.table.split('.')[-1]

spanner_orm/tests/model_test.py

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,6 @@ def test_object_changes(self):
9292
test_model.string_array.append('bat')
9393
self.assertIn('string_array', test_model.changes())
9494

95-
def test_creation_ddl(self):
96-
test_model_ddl = ('CREATE TABLE table (int_ INT64 NOT NULL, int_2 INT64,'
97-
' string STRING(MAX) NOT NULL, string_2 STRING(MAX),'
98-
' timestamp TIMESTAMP NOT NULL, string_array'
99-
' ARRAY<STRING(MAX)>) PRIMARY KEY (int_, string)')
100-
self.assertEqual(models.UnittestModel.creation_ddl, test_model_ddl)
101-
102-
def test_interleaved_creation_ddl(self):
103-
test_model_ddl = ('CREATE TABLE ChildTestModel ('
104-
'parent_key STRING(MAX) NOT NULL, '
105-
'child_key STRING(MAX) NOT NULL) '
106-
'PRIMARY KEY (parent_key, child_key), '
107-
'INTERLEAVE IN PARENT SmallTestModel ON CASCADE DELETE')
108-
self.assertEqual(models.ChildTestModel.creation_ddl, test_model_ddl)
109-
11095
def test_field_exists_on_model_class(self):
11196
self.assertIsInstance(models.SmallTestModel.key, field.Field)
11297
self.assertEqual(models.SmallTestModel.key.field_type(), field.String)
@@ -122,15 +107,15 @@ def test_field_inheritance(self):
122107
self.assertEqual(getattr(test_model, name), value)
123108

124109
def test_relation_get(self):
125-
test_model = models.ChildTestModel({
110+
test_model = models.RelationshipTestModel({
126111
'parent_key': 'parent',
127112
'child_key': 'child',
128113
'parent': []
129114
})
130115
self.assertEqual(test_model.parent, [])
131116

132117
def test_relation_get_error_on_unretrieved(self):
133-
test_model = models.ChildTestModel({
118+
test_model = models.RelationshipTestModel({
134119
'parent_key': 'parent',
135120
'child_key': 'child'
136121
})

spanner_orm/tests/models.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222

2323
class SmallTestModel(model.Model):
24-
"""Model class used for testing"""
24+
"""Model class used for testing."""
2525

2626
__table__ = 'SmallTestModel'
2727
key = field.Field(field.String, primary_key=True)
@@ -30,10 +30,18 @@ class SmallTestModel(model.Model):
3030

3131

3232
class ChildTestModel(model.Model):
33-
"""Model class for testing relationships"""
34-
33+
"""Model class for testing interleaved tables."""
3534
__table__ = 'ChildTestModel'
3635
__interleaved__ = SmallTestModel
36+
37+
key = field.Field(field.String, primary_key=True)
38+
child_key = field.Field(field.String, primary_key=True)
39+
40+
41+
class RelationshipTestModel(model.Model):
42+
"""Model class for testing relationships."""
43+
44+
__table__ = 'RelationshipTestModel'
3745
parent_key = field.Field(field.String, primary_key=True)
3846
child_key = field.Field(field.String, primary_key=True)
3947
parent = relationship.Relationship(
@@ -44,12 +52,12 @@ class ChildTestModel(model.Model):
4452

4553

4654
class InheritanceTestModel(SmallTestModel):
47-
"""Model class used for testing model inheritance"""
55+
"""Model class used for testing model inheritance."""
4856
value_3 = field.Field(field.String, nullable=True)
4957

5058

5159
class UnittestModel(model.Model):
52-
"""Model class used for model testing"""
60+
"""Model class used for model testing."""
5361

5462
__table__ = 'table'
5563
int_ = field.Field(field.Integer, primary_key=True)

spanner_orm/tests/query_test.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -156,30 +156,30 @@ def test_force_index(self):
156156

157157
def includes(self, relation, *conditions):
158158
include_condition = condition.includes(relation, list(conditions))
159-
return query.SelectQuery(models.ChildTestModel, [include_condition])
159+
return query.SelectQuery(models.RelationshipTestModel, [include_condition])
160160

161161
def test_includes(self):
162162
select_query = self.includes('parent')
163163

164164
# The column order varies between test runs
165165
expected_sql = (
166-
r'SELECT ChildTestModel\S* ChildTestModel\S* ARRAY\(SELECT AS '
166+
r'SELECT RelationshipTestModel\S* RelationshipTestModel\S* ARRAY\(SELECT AS '
167167
r'STRUCT SmallTestModel\S* SmallTestModel\S* SmallTestModel\S* FROM '
168168
r'SmallTestModel WHERE SmallTestModel.key = '
169-
r'ChildTestModel.parent_key\)')
169+
r'RelationshipTestModel.parent_key\)')
170170
self.assertRegex(select_query.sql(), expected_sql)
171171
self.assertEmpty(select_query.parameters())
172172
self.assertEmpty(select_query.types())
173173

174174
def test_includes_subconditions_query(self):
175175
select_query = self.includes('parents', condition.equal_to('key', 'value'))
176-
expected_sql = ('WHERE SmallTestModel.key = ChildTestModel.parent_key '
176+
expected_sql = ('WHERE SmallTestModel.key = RelationshipTestModel.parent_key '
177177
'AND SmallTestModel.key = @key0')
178178
self.assertRegex(select_query.sql(), expected_sql)
179179

180180
def includes_result(self, related=1):
181181
child = {'parent_key': 'parent_key', 'child_key': 'child'}
182-
result = [child[name] for name in models.ChildTestModel.columns]
182+
result = [child[name] for name in models.RelationshipTestModel.columns]
183183
parent = {'key': 'key', 'value_1': 'value_1', 'value_2': None}
184184
parents = []
185185
for _ in range(related):

0 commit comments

Comments
 (0)