@@ -46,16 +46,17 @@ class _RenameMap(tuple):
46
46
47
47
class Table (QueryExpression ):
48
48
"""
49
- Table is an abstract class that represents a base relation, i.e. a table in the schema.
49
+ Table is an abstract class that represents a table in the schema.
50
+ It implements insert and delete methods and inherits query functionality.
50
51
To make it a concrete class, override the abstract properties specifying the connection,
51
52
table name, database, and definition.
52
- A Relation implements insert and delete methods in addition to inherited relational operators.
53
53
"""
54
54
55
55
_table_name = None # must be defined in subclass
56
56
_log_ = None # placeholder for the Log table object
57
57
58
- # These properties must be set by the schema decorator (schemas.py) at class level or by FreeTable at instance level
58
+ # These properties must be set by the schema decorator (schemas.py) at class level
59
+ # or by FreeTable at instance level
59
60
database = None
60
61
declaration_context = None
61
62
@@ -65,7 +66,8 @@ def table_name(self):
65
66
66
67
@property
67
68
def definition (self ):
68
- raise NotImplementedError ('Subclasses of Table must implement the `definition` property' )
69
+ raise NotImplementedError (
70
+ 'Subclasses of Table must implement the `definition` property' )
69
71
70
72
def declare (self , context = None ):
71
73
"""
@@ -95,7 +97,8 @@ def alter(self, prompt=True, context=None):
95
97
"""
96
98
if self .connection .in_transaction :
97
99
raise DataJointError (
98
- 'Cannot update table declaration inside a transaction, e.g. from inside a populate/make call' )
100
+ 'Cannot update table declaration inside a transaction, '
101
+ 'e.g. from inside a populate/make call' )
99
102
if context is None :
100
103
frame = inspect .currentframe ().f_back
101
104
context = dict (frame .f_globals , ** frame .f_locals )
@@ -117,7 +120,8 @@ def alter(self, prompt=True, context=None):
117
120
# skip if no create privilege
118
121
pass
119
122
else :
120
- self .__class__ ._heading = Heading (table_info = self .heading .table_info ) # reset heading
123
+ # reset heading
124
+ self .__class__ ._heading = Heading (table_info = self .heading .table_info )
121
125
if prompt :
122
126
print ('Table altered' )
123
127
self ._log ('Altered ' + self .full_table_name )
@@ -226,9 +230,11 @@ def external(self):
226
230
def update1 (self , row ):
227
231
"""
228
232
update1 updates one existing entry in the table.
229
- Caution: Updates are not part of the DataJoint data manipulation model. For strict data integrity,
230
- use delete and insert.
231
- :param row: a dict containing the primary key and the attributes to update.
233
+ Caution: In DataJoint the primary modes for data manipulation is to insert and delete
234
+ entire records since referential integrity works on the level of records, not fields.
235
+ Therefore, updates are reserved for corrective operations outside of main workflow.
236
+ Use UPDATE methods sparingly with full awareness of potential violations of assumptions.
237
+ :param row: a dict containing the primary key values and the attributes to update.
232
238
Setting an attribute value to None will reset it to the default value (if any)
233
239
The primary key attributes must always be provided.
234
240
Examples:
@@ -241,7 +247,8 @@ def update1(self, row):
241
247
if not set (row ).issuperset (self .primary_key ):
242
248
raise DataJointError ('The argument of update1 must supply all primary key values.' )
243
249
try :
244
- raise DataJointError ('Attribute `%s` not found.' % next (k for k in row if k not in self .heading .names ))
250
+ raise DataJointError ('Attribute `%s` not found.' %
251
+ next (k for k in row if k not in self .heading .names ))
245
252
except StopIteration :
246
253
pass # ok
247
254
if len (self .restriction ):
@@ -250,7 +257,8 @@ def update1(self, row):
250
257
if len (self & key ) != 1 :
251
258
raise DataJointError ('Update entry must exist.' )
252
259
# UPDATE query
253
- row = [self .__make_placeholder (k , v ) for k , v in row .items () if k not in self .primary_key ]
260
+ row = [self .__make_placeholder (k , v ) for k , v in row .items ()
261
+ if k not in self .primary_key ]
254
262
query = "UPDATE {table} SET {assignments} WHERE {where}" .format (
255
263
table = self .full_table_name ,
256
264
assignments = "," .join ('`%s`=%s' % r [:2 ] for r in row ),
@@ -259,22 +267,25 @@ def update1(self, row):
259
267
260
268
def insert1 (self , row , ** kwargs ):
261
269
"""
262
- Insert one data record or one Mapping (like a dict).
263
- :param row: a numpy record, a dict-like object, or an ordered sequence to be inserted as one row.
270
+ Insert one data record into the table..
271
+ :param row: a numpy record, a dict-like object, or an ordered sequence to be inserted
272
+ as one row.
264
273
For kwargs, see insert()
265
274
"""
266
275
self .insert ((row ,), ** kwargs )
267
276
268
- def insert (self , rows , replace = False , skip_duplicates = False , ignore_extra_fields = False , allow_direct_insert = None ):
277
+ def insert (self , rows , replace = False , skip_duplicates = False , ignore_extra_fields = False ,
278
+ allow_direct_insert = None ):
269
279
"""
270
280
Insert a collection of rows.
271
- :param rows: An iterable where an element is a numpy record, a dict-like object, a pandas.DataFrame, a sequence,
272
- or a query expression with the same heading as table self.
281
+ :param rows: An iterable where an element is a numpy record, a dict-like object, a
282
+ pandas.DataFrame, a sequence, or a query expression with the same heading as self.
273
283
:param replace: If True, replaces the existing tuple.
274
284
:param skip_duplicates: If True, silently skip duplicate inserts.
275
285
:param ignore_extra_fields: If False, fields that are not in the heading raise error.
276
286
:param allow_direct_insert: applies only in auto-populated tables.
277
- If False (default), insert are allowed only from inside the make callback.
287
+ If False (default), insert are allowed only from inside
288
+ the make callback.
278
289
Example::
279
290
>>> relation.insert([
280
291
>>> dict(subject_id=7, species="mouse", date_of_birth="2014-09-01"),
@@ -288,20 +299,22 @@ def insert(self, rows, replace=False, skip_duplicates=False, ignore_extra_fields
288
299
).to_records (index = False )
289
300
290
301
# prohibit direct inserts into auto-populated tables
291
- if not allow_direct_insert and not getattr (self , '_allow_insert' , True ): # allow_insert is only used in AutoPopulate
302
+ if not allow_direct_insert and not getattr (self , '_allow_insert' , True ):
292
303
raise DataJointError (
293
- 'Inserts into an auto-populated table can only done inside its make method during a populate call.'
304
+ 'Inserts into an auto-populated table can only done inside '
305
+ 'its make method during a populate call.'
294
306
' To override, set keyword argument allow_direct_insert=True.' )
295
307
296
- if inspect .isclass (rows ) and issubclass (rows , QueryExpression ): # instantiate if a class
297
- rows = rows ()
308
+ if inspect .isclass (rows ) and issubclass (rows , QueryExpression ):
309
+ rows = rows () # instantiate if a class
298
310
if isinstance (rows , QueryExpression ):
299
311
# insert from select
300
312
if not ignore_extra_fields :
301
313
try :
302
314
raise DataJointError (
303
- "Attribute %s not found. To ignore extra attributes in insert, set ignore_extra_fields=True." %
304
- next (name for name in rows .heading if name not in self .heading ))
315
+ "Attribute %s not found. To ignore extra attributes in insert, "
316
+ "set ignore_extra_fields=True." % next (
317
+ name for name in rows .heading if name not in self .heading ))
305
318
except StopIteration :
306
319
pass
307
320
fields = list (name for name in rows .heading if name in self .heading )
@@ -369,10 +382,21 @@ def _delete_cascade(self):
369
382
* [_ .strip ('`' ) for _ in match ['child' ].split ('`.`' )]
370
383
)).fetchall ())))
371
384
match ['parent' ] = match ['parent' ][0 ]
372
- # restrict child by self if
373
- # 1. if self's restriction attributes are not in child's primary key
374
- # 2. if child renames any attributes
375
- # otherwise restrict child by self's restriction.
385
+
386
+ # If child is a part table of another table, do not recurse (See issue #151)
387
+ if '__' in match ['child' ] and (not match ['child' ].strip ('`' ).startswith (
388
+ self .full_table_name ).strip ('`' ) + '__' ):
389
+ raise DataJointError (
390
+ 'Attempt to delete from part table {part} before deleting from '
391
+ 'its master. Delete from {master} first.' .format (
392
+ part = match ['child' ],
393
+ master = match ['child' ].split ('__' )[0 ] + '`'
394
+ ))
395
+
396
+ # Restrict child by self if
397
+ # 1. if self's restriction attributes are not in child's primary key
398
+ # 2. if child renames any attributes
399
+ # Otherwise restrict child by self's restriction.
376
400
child = FreeTable (self .connection , match ['child' ])
377
401
if set (self .restriction_attributes ) <= set (child .primary_key ) and \
378
402
match ['fk_attrs' ] == match ['pk_attrs' ]:
@@ -396,6 +420,8 @@ def delete(self, transaction=True, safemode=None):
396
420
Deletes the contents of the table and its dependent tables, recursively.
397
421
398
422
:param transaction: if True, use the entire delete becomes an atomic transaction.
423
+ This is the default and recommended behavior. Set to False if this delete is nested
424
+ within another transaction.
399
425
:param safemode: If True, prohibit nested transactions and prompt to confirm. Default
400
426
is dj.config['safemode'].
401
427
:return: number of deleted rows (excluding those from dependent tables)
@@ -460,7 +486,7 @@ def drop(self):
460
486
User is prompted for confirmation if config['safemode'] is set to True.
461
487
"""
462
488
if self .restriction :
463
- raise DataJointError ('A relation with an applied restriction condition cannot be dropped.'
489
+ raise DataJointError ('A table with an applied restriction cannot be dropped.'
464
490
' Call drop() on the unrestricted Table.' )
465
491
self .connection .dependencies .load ()
466
492
do_drop = True
@@ -555,12 +581,14 @@ def describe(self, context=None, printout=True):
555
581
556
582
def _update (self , attrname , value = None ):
557
583
"""
558
- This is a deprecated function to be removed in datajoint 0.14. Use .update1 instead.
584
+ This is a deprecated function to be removed in datajoint 0.14.
585
+ Use .update1 instead.
559
586
560
- Updates a field in an existing tuple. This is not a datajoyous operation and should not be used
561
- routinely. Relational database maintain referential integrity on the level of a tuple. Therefore,
562
- the UPDATE operator can violate referential integrity. The datajoyous way to update information is
563
- to delete the entire tuple and insert the entire update tuple.
587
+ Updates a field in one existing tuple. self must be restricted to exactly one entry.
588
+ In DataJoint the principal way of updating data is to delete and re-insert the
589
+ entire record and updates are reserved for corrective actions.
590
+ This is because referential integrity is observed on the level of entire
591
+ records rather than individual attributes.
564
592
565
593
Safety constraints:
566
594
1. self must be restricted to exactly one tuple
0 commit comments