Skip to content

Commit 77d392b

Browse files
authored
Merge pull request #54 from 3dcitydb/feature-new-terminate-script
Use new 3DCityDB terminate script
2 parents 8f98366 + db91ffb commit 77d392b

File tree

5 files changed

+189
-40
lines changed

5 files changed

+189
-40
lines changed

citydb-cli/src/main/java/org/citydb/cli/deleter/DeleteCommand.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ enum Mode {delete, terminate}
6363
description = "Delete mode: ${COMPLETION-CANDIDATES} (default: ${DEFAULT-VALUE}).")
6464
private Mode mode;
6565

66+
@CommandLine.Option(names = "--no-terminate-all", negatable = true, defaultValue = "true",
67+
description = "Also terminate sub-features (default: ${DEFAULT-VALUE}).")
68+
private boolean terminateAll;
69+
6670
@CommandLine.Mixin
6771
protected IndexOptions indexOptions;
6872

@@ -220,12 +224,21 @@ private DeleteOptions getDeleteOptions() throws ExecutionException {
220224
if (Command.hasMatchedOption("--delete-mode", commandSpec)) {
221225
deleteOptions.setMode(mode == Mode.terminate ? DeleteMode.TERMINATE : DeleteMode.DELETE);
222226
}
227+
228+
if (Command.hasMatchedOption("--no-terminate-all", commandSpec)) {
229+
deleteOptions.setTerminateWithSubFeatures(terminateAll);
230+
}
223231
} else {
224232
deleteOptions = new DeleteOptions();
225-
deleteOptions.setMode(mode == Mode.terminate ? DeleteMode.TERMINATE : DeleteMode.DELETE);
233+
deleteOptions.setMode(mode == Mode.terminate ? DeleteMode.TERMINATE : DeleteMode.DELETE)
234+
.setTerminateWithSubFeatures(terminateAll);
226235
}
227236

228237
if (metadataOptions != null) {
238+
if (metadataOptions.getTerminationDate() != null) {
239+
deleteOptions.setTerminationDate(metadataOptions.getTerminationDate());
240+
}
241+
229242
if (metadataOptions.getLineage() != null) {
230243
deleteOptions.setLineage(metadataOptions.getLineage());
231244
}

citydb-cli/src/main/java/org/citydb/cli/deleter/options/MetadataOptions.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,17 @@
2222
package org.citydb.cli.deleter.options;
2323

2424
import org.citydb.cli.common.Option;
25+
import org.citydb.core.time.TimeHelper;
2526
import picocli.CommandLine;
2627

28+
import java.time.OffsetDateTime;
29+
2730
public class MetadataOptions implements Option {
31+
@CommandLine.Option(names = "--termination-date",
32+
description = "Time in <YYYY-MM-DD> or <YYYY-MM-DDThh:mm:ss[(+|-)hh:mm]> format to use as " +
33+
"termination date (default: now)")
34+
private String time;
35+
2836
@CommandLine.Option(names = "--lineage",
2937
description = "Lineage to use for the features.")
3038
private String lineage;
@@ -37,6 +45,12 @@ public class MetadataOptions implements Option {
3745
description = "Reason for deleting the data.")
3846
private String reasonForUpdate;
3947

48+
private OffsetDateTime terminationDate;
49+
50+
public OffsetDateTime getTerminationDate() {
51+
return terminationDate;
52+
}
53+
4054
public String getLineage() {
4155
return lineage;
4256
}
@@ -48,4 +62,17 @@ public String getUpdatingPerson() {
4862
public String getReasonForUpdate() {
4963
return reasonForUpdate;
5064
}
65+
66+
@Override
67+
public void preprocess(CommandLine commandLine) throws Exception {
68+
if (time != null) {
69+
try {
70+
terminationDate = OffsetDateTime.parse(time, TimeHelper.DATE_TIME_FORMATTER);
71+
} catch (Exception e) {
72+
throw new CommandLine.ParameterException(commandLine,
73+
"The termination time must be in YYYY-MM-DD or YYYY-MM-DDThh:mm:ss[(+|-)hh:mm] " +
74+
"format but was '" + time + "'");
75+
}
76+
}
77+
}
5178
}

citydb-operation/src/main/java/org/citydb/operation/deleter/DeleteOptions.java

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,11 @@ public class DeleteOptions {
3737
private DeleteMode mode = DeleteMode.DELETE;
3838
private int numberOfThreads;
3939
private int batchSize = DEFAULT_BATCH_SIZE;
40+
private boolean terminateWithSubFeatures = true;
41+
private OffsetDateTime terminationDate;
4042
private String updatingPerson;
4143
private String reasonForUpdate;
4244
private String lineage;
43-
private OffsetDateTime terminationDate;
4445

4546
public DeleteMode getMode() {
4647
return mode != null ? mode : DeleteMode.DELETE;
@@ -69,6 +70,24 @@ public DeleteOptions setBatchSize(int batchSize) {
6970
return this;
7071
}
7172

73+
public boolean isTerminateWithSubFeatures() {
74+
return terminateWithSubFeatures;
75+
}
76+
77+
public DeleteOptions setTerminateWithSubFeatures(boolean terminateWithSubFeatures) {
78+
this.terminateWithSubFeatures = terminateWithSubFeatures;
79+
return this;
80+
}
81+
82+
public Optional<OffsetDateTime> getTerminationDate() {
83+
return Optional.ofNullable(terminationDate);
84+
}
85+
86+
public DeleteOptions setTerminationDate(OffsetDateTime terminationDate) {
87+
this.terminationDate = terminationDate;
88+
return this;
89+
}
90+
7291
public Optional<String> getUpdatingPerson() {
7392
return Optional.ofNullable(updatingPerson);
7493
}
@@ -95,13 +114,4 @@ public DeleteOptions setLineage(String lineage) {
95114
this.lineage = lineage;
96115
return this;
97116
}
98-
99-
public Optional<OffsetDateTime> getTerminationDate() {
100-
return Optional.ofNullable(terminationDate);
101-
}
102-
103-
public DeleteOptions setTerminationDate(OffsetDateTime terminationDate) {
104-
this.terminationDate = terminationDate;
105-
return this;
106-
}
107117
}

citydb-operation/src/main/java/org/citydb/operation/deleter/feature/FeatureDeleter.java

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

2222
package org.citydb.operation.deleter.feature;
2323

24-
import org.citydb.database.schema.Table;
24+
import com.alibaba.fastjson2.JSONObject;
2525
import org.citydb.operation.deleter.DeleteException;
2626
import org.citydb.operation.deleter.DeleteHelper;
2727
import org.citydb.operation.deleter.common.DatabaseDeleter;
@@ -30,32 +30,27 @@
3030
import java.sql.Connection;
3131
import java.sql.PreparedStatement;
3232
import java.sql.SQLException;
33+
import java.sql.Types;
3334
import java.time.OffsetDateTime;
3435

3536
public class FeatureDeleter extends DatabaseDeleter {
37+
private final JSONObject metadata = new JSONObject();
3638

3739
public FeatureDeleter(DeleteHelper helper) throws SQLException {
3840
super(helper);
41+
helper.getOptions().getReasonForUpdate().ifPresent(reasonForUpdate ->
42+
metadata.put("reason_for_update", reasonForUpdate));
43+
helper.getOptions().getLineage().ifPresent(lineage ->
44+
metadata.put("lineage", lineage));
45+
metadata.put("updating_person", helper.getOptions().getUpdatingPerson()
46+
.orElseGet(helper.getAdapter().getConnectionDetails()::getUser));
3947
}
4048

4149
@Override
4250
protected PreparedStatement getDeleteStatement(Connection connection) throws SQLException {
43-
if (helper.getOptions().getMode() == DeleteMode.TERMINATE) {
44-
StringBuilder stmt = new StringBuilder("update ")
45-
.append(helper.getTableHelper().getPrefixedTableName(Table.FEATURE))
46-
.append(" set termination_date = ?, last_modification_date = ?, updating_person = ?");
47-
48-
helper.getOptions().getReasonForUpdate().ifPresent(reasonForUpdate ->
49-
stmt.append(", reason_for_update = '").append(reasonForUpdate).append("'"));
50-
51-
helper.getOptions().getLineage().ifPresent(lineage ->
52-
stmt.append(", lineage = '").append(lineage).append("'"));
53-
54-
stmt.append(" where id = ?");
55-
return connection.prepareStatement(stmt.toString());
56-
} else {
57-
return connection.prepareCall("{call citydb_pkg.delete_feature(?, ?)}");
58-
}
51+
return helper.getOptions().getMode() == DeleteMode.TERMINATE ?
52+
connection.prepareCall("{call citydb_pkg.terminate_feature(?, ?, ?, ?)}") :
53+
connection.prepareCall("{call citydb_pkg.delete_feature(?, ?)}");
5954
}
6055

6156
public void deleteFeature(long id) throws DeleteException, SQLException {
@@ -65,19 +60,14 @@ public void deleteFeature(long id) throws DeleteException, SQLException {
6560
@Override
6661
protected void executeBatch(Long[] ids) throws SQLException {
6762
if (helper.getOptions().getMode() == DeleteMode.TERMINATE) {
68-
String updatingPerson = helper.getOptions().getUpdatingPerson()
69-
.orElse(helper.getAdapter().getConnectionDetails().getUser());
70-
71-
for (long id : ids) {
72-
OffsetDateTime now = OffsetDateTime.now().withNano(0);
73-
stmt.setObject(1, helper.getOptions().getTerminationDate().orElse(now));
74-
stmt.setObject(2, now);
75-
stmt.setString(3, updatingPerson);
76-
stmt.setLong(4, id);
77-
stmt.addBatch();
78-
}
63+
metadata.put("termination_date", helper.getOptions().getTerminationDate()
64+
.orElseGet(() -> OffsetDateTime.now().withNano(0)));
7965

80-
stmt.executeBatch();
66+
stmt.setArray(1, helper.getConnection().createArrayOf("bigint", ids));
67+
stmt.setString(2, helper.getAdapter().getConnectionDetails().getSchema());
68+
stmt.setObject(3, metadata.toString(), Types.OTHER);
69+
stmt.setBoolean(4, helper.getOptions().isTerminateWithSubFeatures());
70+
stmt.execute();
8171
} else {
8272
stmt.setArray(1, helper.getConnection().createArrayOf("bigint", ids));
8373
stmt.setString(2, helper.getAdapter().getConnectionDetails().getSchema());

resources/3dcitydb/postgresql/sql-scripts/citydb-pkg/delete.sql

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@
3636
* delete_address(pid_array bigint[], schema_name TEXT) RETURNS SETOF BIGINT
3737
* delete_address(pid bigint) RETURNS BIGINT
3838
* delete_address(pid bigint, schema_name TEXT) RETURNS BIGINT
39+
* terminate_feature(pid_array bigint[], metadata JSON DEFAULT '{}', cascade BOOLEAN DEFAULT TRUE) RETURNS SETOF BIGINT
40+
* terminate_feature(pid_array bigint[], schema_name TEXT, metadata JSON DEFAULT '{}', cascade BOOLEAN DEFAULT TRUE) RETURNS SETOF BIGINT
41+
* terminate_feature(pid bigint, metadata JSON DEFAULT '{}', cascade BOOLEAN DEFAULT TRUE) RETURNS BIGINT
42+
* terminate_feature(pid bigint, schema_name TEXT, metadata JSON DEFAULT '{}', cascade BOOLEAN DEFAULT TRUE) RETURNS BIGINT
3943
******************************************************************/
4044

4145
/******************************************************************
@@ -796,4 +800,109 @@ BEGIN
796800
RETURN citydb_pkg.del_address(ARRAY[pid]);
797801
END;
798802
$body$
803+
LANGUAGE plpgsql STRICT;
804+
805+
/******************************************************************
806+
* terminate feature based on an id array
807+
******************************************************************/
808+
CREATE OR REPLACE FUNCTION citydb_pkg.terminate_feature(pid_array bigint[], metadata JSON DEFAULT '{}', cascade BOOLEAN DEFAULT TRUE) RETURNS SETOF BIGINT AS
809+
$body$
810+
DECLARE
811+
terminated_ids bigint[] := '{}';
812+
child_feature_ids bigint[] := '{}';
813+
BEGIN
814+
WITH terminated_objects AS (
815+
UPDATE
816+
feature f
817+
SET
818+
termination_date = COALESCE((metadata->>'termination_date')::timestamp with time zone, now()),
819+
last_modification_date = COALESCE((metadata->>'last_modification_date')::timestamp with time zone, now()),
820+
reason_for_update = COALESCE(metadata->>'reason_for_update', f.reason_for_update),
821+
updating_person = COALESCE(metadata->>'updating_person', USER),
822+
lineage = COALESCE(metadata->>'lineage', f.lineage)
823+
FROM
824+
unnest($1) a(a_id)
825+
WHERE
826+
f.id = a.a_id
827+
RETURNING
828+
f.id
829+
)
830+
SELECT
831+
array_agg(id)
832+
INTO
833+
terminated_ids
834+
FROM
835+
terminated_objects;
836+
837+
if cascade THEN
838+
SELECT
839+
array_agg(val_feature_id)
840+
INTO
841+
child_feature_ids
842+
FROM
843+
property p, unnest(terminated_ids) a(a_id)
844+
WHERE
845+
p.feature_id = a.a_id AND val_relation_type = 1;
846+
847+
IF -1 = ALL(child_feature_ids) IS NOT NULL THEN
848+
PERFORM
849+
citydb_pkg.terminate_feature(array_agg(a.a_id), metadata, cascade)
850+
FROM
851+
(SELECT DISTINCT unnest(child_feature_ids) AS a_id) a
852+
WHERE NOT EXISTS
853+
(
854+
SELECT
855+
1
856+
FROM
857+
property p
858+
INNER JOIN
859+
feature f
860+
ON f.id = p.feature_id
861+
WHERE p.val_feature_id = a.a_id AND f.termination_date IS NULL AND p.val_relation_type = 1
862+
);
863+
END IF;
864+
END IF;
865+
866+
RETURN QUERY
867+
SELECT unnest(terminated_ids);
868+
END;
869+
$body$
870+
LANGUAGE plpgsql STRICT;
871+
872+
/******************************************************************
873+
* terminate features based on an id array and schema name
874+
******************************************************************/
875+
CREATE OR REPLACE FUNCTION citydb_pkg.terminate_feature(pid_array bigint[], schema_name TEXT, metadata JSON DEFAULT '{}', cascade BOOLEAN DEFAULT TRUE) RETURNS SETOF BIGINT AS
876+
$body$
877+
BEGIN
878+
EXECUTE format('set search_path to %I, public', schema_name);
879+
880+
RETURN QUERY
881+
SELECT citydb_pkg.terminate_feature(pid_array, metadata, cascade);
882+
END;
883+
$body$
884+
LANGUAGE plpgsql STRICT;
885+
886+
/******************************************************************
887+
* terminate a feature based on an id and schema name
888+
******************************************************************/
889+
CREATE OR REPLACE FUNCTION citydb_pkg.terminate_feature(pid bigint, metadata JSON DEFAULT '{}', cascade BOOLEAN DEFAULT TRUE) RETURNS BIGINT AS
890+
$body$
891+
BEGIN
892+
RETURN citydb_pkg.terminate_feature(ARRAY[pid], metadata, cascade);
893+
END;
894+
$body$
895+
LANGUAGE plpgsql STRICT;
896+
897+
/******************************************************************
898+
* terminate a feature based on an id and schema name
899+
******************************************************************/
900+
CREATE OR REPLACE FUNCTION citydb_pkg.terminate_feature(pid bigint, schema_name TEXT, metadata JSON DEFAULT '{}', cascade BOOLEAN DEFAULT TRUE) RETURNS BIGINT AS
901+
$body$
902+
BEGIN
903+
EXECUTE format('set search_path to %I, public', schema_name);
904+
905+
RETURN citydb_pkg.terminate_feature(ARRAY[pid], metadata, cascade);
906+
END;
907+
$body$
799908
LANGUAGE plpgsql STRICT;

0 commit comments

Comments
 (0)