@@ -186,14 +186,18 @@ public String update(Integer id, String json, boolean autoCommit) throws SQLExce
186
186
referencedPatternUsesFrequencies = selectResults .getBoolean (1 );
187
187
}
188
188
}
189
- updateChildTable (
189
+ String keyValue = updateChildTable (
190
190
childEntitiesArray ,
191
191
entityId ,
192
192
referencedPatternUsesFrequencies ,
193
193
isCreating ,
194
194
referencingTable ,
195
195
connection
196
196
);
197
+ // Ensure JSON return object is updated with referencing table's (potentially) new key value.
198
+ // Currently, the only case where an update occurs is when a referenced shape is referenced by other
199
+ // patterns.
200
+ jsonObject .put (referencingTable .getKeyFieldName (), keyValue );
197
201
}
198
202
}
199
203
// Iterate over table's fields and apply linked values to any tables. This is to account for "exemplar"
@@ -441,21 +445,19 @@ private void setStatementParameters(
441
445
* have a hierarchical relationship.
442
446
* FIXME develop a better way to update tables with foreign keys to the table being updated.
443
447
*/
444
- private void updateChildTable (
448
+ private String updateChildTable (
445
449
ArrayNode subEntities ,
446
450
int id ,
447
451
boolean referencedPatternUsesFrequencies ,
448
452
boolean isCreatingNewEntity ,
449
453
Table subTable ,
450
454
Connection connection
451
455
) throws SQLException , IOException {
452
- // Get parent table's key field
453
- Field keyField ;
454
- String keyValue ;
455
- // Primary key fields are always referenced by foreign key fields with the same name.
456
- keyField = specTable .getFieldForName (subTable .getKeyFieldName ());
456
+ // Get parent table's key field. Primary key fields are always referenced by foreign key fields with the same
457
+ // name.
458
+ Field keyField = specTable .getFieldForName (subTable .getKeyFieldName ());
457
459
// Get parent entity's key value
458
- keyValue = getValueForId (id , keyField .name , tablePrefix , specTable , connection );
460
+ String keyValue = getValueForId (id , keyField .name , tablePrefix , specTable , connection );
459
461
String childTableName = String .join ("." , tablePrefix , subTable .name );
460
462
if (!referencedPatternUsesFrequencies && subTable .name .equals (Table .FREQUENCIES .name ) && subEntities .size () > 0 ) {
461
463
// Do not permit the illegal state where frequency entries are being added/modified for a timetable pattern.
@@ -479,17 +481,44 @@ private void updateChildTable(
479
481
reconcilePatternStops (keyValue , newPatternStops , connection );
480
482
}
481
483
if (!isCreatingNewEntity ) {
482
- // Delete existing sub-entities for given entity ID if the parent entity is not being newly created.
483
- String deleteSql = getUpdateReferencesSql (SqlMethod .DELETE , childTableName , keyField , keyValue , null );
484
- LOG .info (deleteSql );
485
- Statement statement = connection .createStatement ();
486
- // FIXME: Use copy on update for a pattern's shape instead of deleting the previous shape and replacing it.
487
- // This would better account for GTFS data loaded from a file where multiple patterns reference a single
488
- // shape.
489
- int result = statement .executeUpdate (deleteSql );
490
- LOG .info ("Deleted {} {}" , result , subTable .name );
491
- // FIXME: are there cases when an update should not return zero?
492
- // if (result == 0) throw new SQLException("No stop times found for trip ID");
484
+ // If not creating a new entity, we will delete the child entities (e.g., shape points or pattern stops) and
485
+ // regenerate them anew to avoid any messiness that we may encounter with update statements.
486
+ if (Table .SHAPES .name .equals (subTable .name )) {
487
+ // Check how many patterns are referencing the same shape_id to determine if we should copy on update.
488
+ String patternsForShapeIdSql = String .format ("select id from %s.patterns where shape_id = ?" , tablePrefix );
489
+ PreparedStatement statement = connection .prepareStatement (patternsForShapeIdSql );
490
+ statement .setString (1 , keyValue );
491
+ LOG .info (statement .toString ());
492
+ ResultSet resultSet = statement .executeQuery ();
493
+ int patternsForShapeId = 0 ;
494
+ while (resultSet .next ()) {
495
+ patternsForShapeId ++;
496
+ }
497
+ if (patternsForShapeId > 1 ) {
498
+ // Use copy on update for pattern shape if a single shape is being used for multiple patterns because
499
+ // we do not want edits for a given pattern (for example, a short run) to impact the shape for another
500
+ // pattern (perhaps a long run that extends farther). Note: this behavior will have the side effect of
501
+ // creating potentially redundant shape information, but this is better than accidentally butchering the
502
+ // shapes for other patterns.
503
+ LOG .info ("More than one pattern references shape_id: {}" , keyValue );
504
+ keyValue = UUID .randomUUID ().toString ();
505
+ LOG .info ("Creating new shape_id ({}) for pattern id={}." , keyValue , id );
506
+ // Update pattern#shape_id with new value. Note: shape_point#shape_id values are coerced to new
507
+ // value further down in this function.
508
+ String updatePatternShapeIdSql = String .format ("update %s.patterns set shape_id = ? where id = ?" , tablePrefix );
509
+ PreparedStatement updateStatement = connection .prepareStatement (updatePatternShapeIdSql );
510
+ updateStatement .setString (1 , keyValue );
511
+ updateStatement .setInt (2 , id );
512
+ LOG .info (updateStatement .toString ());
513
+ updateStatement .executeUpdate ();
514
+ } else {
515
+ // If only one pattern references this shape, delete the existing shape points to start anew.
516
+ deleteChildEntities (subTable , keyField , keyValue );
517
+ }
518
+ } else {
519
+ // If not handling shape points, delete the child entities and create them anew.
520
+ deleteChildEntities (subTable , keyField , keyValue );
521
+ }
493
522
}
494
523
int entityCount = 0 ;
495
524
PreparedStatement insertStatement = null ;
@@ -587,6 +616,25 @@ private void updateChildTable(
587
616
} else {
588
617
LOG .info ("No inserts to execute. Empty array found in JSON for child table {}" , childTableName );
589
618
}
619
+ // Return key value in the case that it was updated (the only case for this would be if the shape was referenced
620
+ // by multiple patterns).
621
+ return keyValue ;
622
+ }
623
+
624
+ /**
625
+ * Delete existing sub-entities for given key value for when an update to the parent entity is made (i.e., the parent
626
+ * entity is not being newly created). Examples of sub-entities include stop times for trips, pattern stops for a
627
+ * pattern, or shape points (for a pattern in our model).
628
+ */
629
+ private void deleteChildEntities (Table childTable , Field keyField , String keyValue ) throws SQLException {
630
+ String childTableName = String .join ("." , tablePrefix , childTable .name );
631
+ String deleteSql = getUpdateReferencesSql (SqlMethod .DELETE , childTableName , keyField , keyValue , null );
632
+ LOG .info (deleteSql );
633
+ Statement deleteStatement = connection .createStatement ();
634
+ int result = deleteStatement .executeUpdate (deleteSql );
635
+ LOG .info ("Deleted {} {}" , result , childTable .name );
636
+ // FIXME: are there cases when an update should not return zero?
637
+ // if (result == 0) throw new SQLException("No stop times found for trip ID");
590
638
}
591
639
592
640
/**
0 commit comments