This is an automated email from the ASF dual-hosted git repository. yuqi1129 pushed a commit to branch feat/12166-occ-drop-cas in repository https://gitbox.apache.org/repos/asf/gravitino.git
commit 6779604b6158b4c4887eb2fc7a13d00c656c76e8 Author: yuqi <[email protected]> AuthorDate: Fri Jul 24 09:41:56 2026 +0800 [#12166] improvement(core): add OCC version columns to model_meta and wire version CAS model_meta had no current_version/last_version, so model could not use version CAS. Add both columns (mysql/postgresql/h2 base schema + 1.3.0->2.0.0 upgrade scripts, DEFAULT 1), add the fields to ModelPO, initialize to 1, and raise them on every updateModel. insertModelVersion (which modifies the model via model_latest_version) now also bumps current_version atomically, so a concurrent updateModel detects it. updateModelMeta uses version-only CAS; the model PO SELECTs project the versions. Tests: POConverter increment test; a service test asserting insert=1, updateModel=2, add-version=3. Completes the entity coverage for #12166. --- .../provider/base/ModelMetaBaseSQLProvider.java | 39 ++++++++-------- .../postgresql/ModelMetaPostgreSQLProvider.java | 14 ++---- .../gravitino/storage/relational/po/ModelPO.java | 14 ++++++ .../storage/relational/utils/POConverters.java | 6 +++ .../relational/service/TestModelMetaService.java | 53 ++++++++++++++++++++++ .../storage/relational/utils/TestPOConverters.java | 37 +++++++++++++++ scripts/h2/schema-2.0.0-h2.sql | 2 + scripts/h2/upgrade-1.3.0-to-2.0.0-h2.sql | 3 ++ scripts/mysql/schema-2.0.0-mysql.sql | 2 + scripts/mysql/upgrade-1.3.0-to-2.0.0-mysql.sql | 4 ++ scripts/postgresql/schema-2.0.0-postgresql.sql | 4 ++ .../upgrade-1.3.0-to-2.0.0-postgresql.sql | 5 ++ 12 files changed, 155 insertions(+), 28 deletions(-) diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelMetaBaseSQLProvider.java b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelMetaBaseSQLProvider.java index 3f9848ae98..637fde8064 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelMetaBaseSQLProvider.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/base/ModelMetaBaseSQLProvider.java @@ -63,8 +63,9 @@ public class ModelMetaBaseSQLProvider { public String listModelPOsBySchemaId(@Param("schemaId") Long schemaId) { return "SELECT model_id AS modelId, model_name AS modelName, metalake_id AS metalakeId," + " catalog_id AS catalogId, schema_id AS schemaId, model_comment AS modelComment," - + " model_properties AS modelProperties, model_latest_version AS" - + " modelLatestVersion, audit_info AS auditInfo, deleted_at AS deletedAt" + + " model_properties AS modelProperties, model_latest_version AS modelLatestVersion," + + " current_version AS currentVersion, last_version AS lastVersion," + + " audit_info AS auditInfo, deleted_at AS deletedAt" + " FROM " + ModelMetaMapper.TABLE_NAME + " WHERE schema_id = #{schemaId} AND deleted_at = 0"; @@ -114,8 +115,9 @@ public class ModelMetaBaseSQLProvider { return "<script>" + " SELECT model_id AS modelId, model_name AS modelName, metalake_id AS metalakeId," + " catalog_id AS catalogId, schema_id AS schemaId, model_comment AS modelComment," - + " model_properties AS modelProperties, model_latest_version AS" - + " modelLatestVersion, audit_info AS auditInfo, deleted_at AS deletedAt" + + " model_properties AS modelProperties, model_latest_version AS modelLatestVersion," + + " current_version AS currentVersion, last_version AS lastVersion," + + " audit_info AS auditInfo, deleted_at AS deletedAt" + " FROM " + ModelMetaMapper.TABLE_NAME + " WHERE deleted_at = 0" @@ -131,8 +133,9 @@ public class ModelMetaBaseSQLProvider { @Param("schemaId") Long schemaId, @Param("modelName") String modelName) { return "SELECT model_id AS modelId, model_name AS modelName, metalake_id AS metalakeId," + " catalog_id AS catalogId, schema_id AS schemaId, model_comment AS modelComment," - + " model_properties AS modelProperties, model_latest_version AS" - + " modelLatestVersion, audit_info AS auditInfo, deleted_at AS deletedAt" + + " model_properties AS modelProperties, model_latest_version AS modelLatestVersion," + + " current_version AS currentVersion, last_version AS lastVersion," + + " audit_info AS auditInfo, deleted_at AS deletedAt" + " FROM " + ModelMetaMapper.TABLE_NAME + " WHERE schema_id = #{schemaId} AND model_name = #{modelName} AND deleted_at = 0"; @@ -191,8 +194,9 @@ public class ModelMetaBaseSQLProvider { public String selectModelMetaByModelId(@Param("modelId") Long modelId) { return "SELECT model_id AS modelId, model_name AS modelName, metalake_id AS metalakeId," + " catalog_id AS catalogId, schema_id AS schemaId, model_comment AS modelComment," - + " model_properties AS modelProperties, model_latest_version AS" - + " modelLatestVersion, audit_info AS auditInfo, deleted_at AS deletedAt" + + " model_properties AS modelProperties, model_latest_version AS modelLatestVersion," + + " current_version AS currentVersion, last_version AS lastVersion," + + " audit_info AS auditInfo, deleted_at AS deletedAt" + " FROM " + ModelMetaMapper.TABLE_NAME + " WHERE model_id = #{modelId} AND deleted_at = 0"; @@ -247,7 +251,10 @@ public class ModelMetaBaseSQLProvider { public String updateModelLatestVersion(@Param("modelId") Long modelId) { return "UPDATE " + ModelMetaMapper.TABLE_NAME - + " SET model_latest_version = model_latest_version + 1" + // Adding a model version modifies the model: also raise the OCC version so a concurrent + // updateModel (WHERE current_version = old) detects it. The +1s are atomic in the database. + + " SET model_latest_version = model_latest_version + 1," + + " current_version = current_version + 1, last_version = last_version + 1" + " WHERE model_id = #{modelId} AND deleted_at = 0"; } @@ -262,18 +269,14 @@ public class ModelMetaBaseSQLProvider { + " model_comment = #{newModelMeta.modelComment}," + " model_properties = #{newModelMeta.modelProperties}," + " model_latest_version = #{newModelMeta.modelLatestVersion}," + + " current_version = #{newModelMeta.currentVersion}," + + " last_version = #{newModelMeta.lastVersion}," + " audit_info = #{newModelMeta.auditInfo}," + " deleted_at = #{newModelMeta.deletedAt}" + // OCC: compare-and-set on the version alone. updateModel always raises current_version, + // and insertModelVersion also bumps it, so this catches both concurrent-write kinds. + " WHERE model_id = #{oldModelMeta.modelId}" - + " AND model_name = #{oldModelMeta.modelName}" - + " AND metalake_id = #{oldModelMeta.metalakeId}" - + " AND catalog_id = #{oldModelMeta.catalogId}" - + " AND schema_id = #{oldModelMeta.schemaId}" - + " AND (model_comment = #{oldModelMeta.modelComment}" - + " OR (model_comment IS NULL and #{oldModelMeta.modelComment} IS NULL))" - + " AND model_properties = #{oldModelMeta.modelProperties}" - + " AND model_latest_version = #{oldModelMeta.modelLatestVersion}" - + " AND audit_info = #{oldModelMeta.auditInfo}" + + " AND current_version = #{oldModelMeta.currentVersion}" + " AND deleted_at = 0"; } diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/ModelMetaPostgreSQLProvider.java b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/ModelMetaPostgreSQLProvider.java index 62d956d0a1..b70cdf0324 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/ModelMetaPostgreSQLProvider.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/mapper/provider/postgresql/ModelMetaPostgreSQLProvider.java @@ -109,19 +109,13 @@ public class ModelMetaPostgreSQLProvider extends ModelMetaBaseSQLProvider { + " model_comment = #{newModelMeta.modelComment}," + " model_properties = #{newModelMeta.modelProperties}," + " model_latest_version = #{newModelMeta.modelLatestVersion}," + + " current_version = #{newModelMeta.currentVersion}," + + " last_version = #{newModelMeta.lastVersion}," + " audit_info = #{newModelMeta.auditInfo}," + " deleted_at = #{newModelMeta.deletedAt}" + // OCC: compare-and-set on the version alone (see the base provider for rationale). + " WHERE model_id = #{oldModelMeta.modelId}" - + " AND model_name = #{oldModelMeta.modelName}" - + " AND metalake_id = #{oldModelMeta.metalakeId}" - + " AND catalog_id = #{oldModelMeta.catalogId}" - + " AND schema_id = #{oldModelMeta.schemaId}" - + " AND (model_comment = #{oldModelMeta.modelComment}" - + " OR (CAST(model_comment AS VARCHAR) IS NULL" - + " AND CAST(#{oldModelMeta.modelComment} AS VARCHAR) IS NULL))" - + " AND model_properties = #{oldModelMeta.modelProperties}" - + " AND model_latest_version = #{oldModelMeta.modelLatestVersion}" - + " AND audit_info = #{oldModelMeta.auditInfo}" + + " AND current_version = #{oldModelMeta.currentVersion}" + " AND deleted_at = 0"; } } diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/po/ModelPO.java b/core/src/main/java/org/apache/gravitino/storage/relational/po/ModelPO.java index 008b071d81..d2125c4db7 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/po/ModelPO.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/po/ModelPO.java @@ -41,6 +41,10 @@ public class ModelPO { private Integer modelLatestVersion; + private Long currentVersion; + + private Long lastVersion; + private String modelProperties; private String auditInfo; @@ -96,6 +100,16 @@ public class ModelPO { return this; } + public Builder withCurrentVersion(Long currentVersion) { + modelPO.currentVersion = currentVersion; + return this; + } + + public Builder withLastVersion(Long lastVersion) { + modelPO.lastVersion = lastVersion; + return this; + } + public Builder withModelProperties(String modelProperties) { modelPO.modelProperties = modelProperties; return this; diff --git a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java index 0327a38cce..cdddd74916 100644 --- a/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java +++ b/core/src/main/java/org/apache/gravitino/storage/relational/utils/POConverters.java @@ -1627,6 +1627,8 @@ public class POConverters { .withModelName(modelEntity.name()) .withModelComment(modelEntity.comment()) .withModelLatestVersion(modelEntity.latestVersion()) + .withCurrentVersion(INIT_VERSION) + .withLastVersion(INIT_VERSION) .withModelProperties( JsonUtils.anyFieldMapper().writeValueAsString(modelEntity.properties())) .withAuditInfo(JsonUtils.anyFieldMapper().writeValueAsString(modelEntity.auditInfo())) @@ -1688,6 +1690,10 @@ public class POConverters { .withSchemaId(oldModelPO.getSchemaId()) .withModelComment(newModel.comment()) .withModelLatestVersion(newModel.latestVersion()) + // Raise the version on every successful update so OCC (version CAS) can detect a + // concurrent write: the racing updater's `WHERE current_version = old` matches 0 rows. + .withCurrentVersion(oldModelPO.getLastVersion() + 1) + .withLastVersion(oldModelPO.getLastVersion() + 1) .withModelProperties(JsonUtils.anyFieldMapper().writeValueAsString(newModel.properties())) .withAuditInfo(JsonUtils.anyFieldMapper().writeValueAsString(newModel.auditInfo())) .withDeletedAt(DEFAULT_DELETED_AT) diff --git a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestModelMetaService.java b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestModelMetaService.java index c1b85ccfe2..d4140ef635 100644 --- a/core/src/test/java/org/apache/gravitino/storage/relational/service/TestModelMetaService.java +++ b/core/src/test/java/org/apache/gravitino/storage/relational/service/TestModelMetaService.java @@ -21,6 +21,7 @@ package org.apache.gravitino.storage.relational.service; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.time.Instant; @@ -34,6 +35,8 @@ import org.apache.gravitino.Namespace; import org.apache.gravitino.exceptions.NoSuchEntityException; import org.apache.gravitino.meta.BaseMetalake; import org.apache.gravitino.meta.ModelEntity; +import org.apache.gravitino.meta.ModelVersionEntity; +import org.apache.gravitino.model.ModelVersion; import org.apache.gravitino.storage.RandomIdGenerator; import org.apache.gravitino.storage.relational.TestJDBCBackend; import org.apache.gravitino.storage.relational.po.ModelPO; @@ -52,6 +55,56 @@ public class TestModelMetaService extends TestJDBCBackend { private static final Namespace MODEL_NS = Namespace.of(METALAKE_NAME, CATALOG_NAME, SCHEMA_NAME); + @TestTemplate + public void testModelUpdateAndVersionAddRaiseCurrentVersion() throws IOException { + createAndInsertMakeLake(METALAKE_NAME); + createAndInsertCatalog(METALAKE_NAME, CATALOG_NAME); + createAndInsertSchema(METALAKE_NAME, CATALOG_NAME, SCHEMA_NAME); + + ModelEntity model = + createModelEntity( + RandomIdGenerator.INSTANCE.nextId(), + MODEL_NS, + "cas_model", + "comment", + 0, + ImmutableMap.of(), + AUDIT_INFO); + ModelMetaService.getInstance().insertModel(model, false); + Assertions.assertEquals( + 1L, ModelMetaService.getInstance().getModelPOById(model.id()).getCurrentVersion()); + + // updateModel raises current_version (1 -> 2). + ModelEntity updated = + createModelEntity( + model.id(), + model.namespace(), + model.name(), + "comment2", + 0, + ImmutableMap.of(), + AUDIT_INFO); + Function<ModelEntity, ModelEntity> updater = old -> updated; + ModelMetaService.getInstance().updateModel(model.nameIdentifier(), updater); + Assertions.assertEquals( + 2L, ModelMetaService.getInstance().getModelPOById(model.id()).getCurrentVersion()); + + // Adding a model version modifies the model, so it also raises current_version (2 -> 3). + ModelVersionEntity version = + ModelVersionEntity.builder() + .withModelIdentifier(model.nameIdentifier()) + .withVersion(0) + .withUris(ImmutableMap.of(ModelVersion.URI_NAME_UNKNOWN, "model_path")) + .withAliases(ImmutableList.of()) + .withComment("version comment") + .withProperties(ImmutableMap.of()) + .withAuditInfo(AUDIT_INFO) + .build(); + ModelVersionMetaService.getInstance().insertModelVersion(version); + Assertions.assertEquals( + 3L, ModelMetaService.getInstance().getModelPOById(model.id()).getCurrentVersion()); + } + @TestTemplate public void testMetaLifeCycleFromCreationToDeletion() throws IOException { BaseMetalake metalake = createAndInsertMakeLake(METALAKE_NAME); diff --git a/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java b/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java index 4999aa2702..165b4ca47c 100644 --- a/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java +++ b/core/src/test/java/org/apache/gravitino/storage/relational/utils/TestPOConverters.java @@ -805,6 +805,43 @@ public class TestPOConverters { assertEquals(2, updatePO.getLastVersion()); } + @Test + public void testUpdateModelPOVersionIncrements() throws JsonProcessingException { + AuditInfo auditInfo = + AuditInfo.builder().withCreator("creator").withCreateTime(FIX_INSTANT).build(); + Namespace ns = Namespace.of("test_metalake", "test_catalog", "test_schema"); + ModelEntity model = + ModelEntity.builder() + .withId(1L) + .withName("test") + .withNamespace(ns) + .withComment("this is test") + .withProperties(ImmutableMap.of("key", "value")) + .withLatestVersion(1) + .withAuditInfo(auditInfo) + .build(); + ModelEntity updatedModel = + ModelEntity.builder() + .withId(1L) + .withName("test") + .withNamespace(ns) + .withComment("this is test2") + .withProperties(ImmutableMap.of("key", "value")) + .withLatestVersion(1) + .withAuditInfo(auditInfo) + .build(); + ModelPO initPO = + POConverters.initializeModelPO( + model, ModelPO.builder().withMetalakeId(1L).withCatalogId(1L).withSchemaId(1L)); + assertEquals(1L, initPO.getCurrentVersion()); + + ModelPO updatePO = POConverters.updateModelPO(initPO, updatedModel); + + // A successful update must raise the version so OCC (version CAS) can detect concurrent writes. + assertEquals(2L, updatePO.getCurrentVersion()); + assertEquals(2L, updatePO.getLastVersion()); + } + @Test public void testUpdateFilesetPOVersion() throws JsonProcessingException { Map<String, String> properties = new HashMap<>(); diff --git a/scripts/h2/schema-2.0.0-h2.sql b/scripts/h2/schema-2.0.0-h2.sql index 8b2bb32b9a..ef6e4d74cc 100644 --- a/scripts/h2/schema-2.0.0-h2.sql +++ b/scripts/h2/schema-2.0.0-h2.sql @@ -346,6 +346,8 @@ CREATE TABLE IF NOT EXISTS `model_meta` ( `model_comment` CLOB DEFAULT NULL COMMENT 'model comment', `model_properties` CLOB DEFAULT NULL COMMENT 'model properties', `model_latest_version` INT UNSIGNED DEFAULT 0 COMMENT 'model latest version', + `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'model current version', + `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'model last version', `audit_info` CLOB NOT NULL COMMENT 'model audit info', `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'model deleted at', PRIMARY KEY (`model_id`), diff --git a/scripts/h2/upgrade-1.3.0-to-2.0.0-h2.sql b/scripts/h2/upgrade-1.3.0-to-2.0.0-h2.sql index a05b05c59c..92eaf4e664 100644 --- a/scripts/h2/upgrade-1.3.0-to-2.0.0-h2.sql +++ b/scripts/h2/upgrade-1.3.0-to-2.0.0-h2.sql @@ -24,3 +24,6 @@ ALTER TABLE `group_meta` ADD COLUMN `external_id` VARCHAR(256) DEFAULT NULL COMM CREATE UNIQUE INDEX IF NOT EXISTS `uk_mid_ueid_del` ON `user_meta` (`metalake_id`, `external_id`, `deleted_at`); CREATE UNIQUE INDEX IF NOT EXISTS `uk_mid_geid_del` ON `group_meta` (`metalake_id`, `external_id`, `deleted_at`); + +ALTER TABLE `model_meta` ADD COLUMN `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'model current version' AFTER `model_latest_version`; +ALTER TABLE `model_meta` ADD COLUMN `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'model last version' AFTER `current_version`; diff --git a/scripts/mysql/schema-2.0.0-mysql.sql b/scripts/mysql/schema-2.0.0-mysql.sql index a1822aca69..17d1c85543 100644 --- a/scripts/mysql/schema-2.0.0-mysql.sql +++ b/scripts/mysql/schema-2.0.0-mysql.sql @@ -337,6 +337,8 @@ CREATE TABLE IF NOT EXISTS `model_meta` ( `model_comment` TEXT DEFAULT NULL COMMENT 'model comment', `model_properties` MEDIUMTEXT DEFAULT NULL COMMENT 'model properties', `model_latest_version` INT UNSIGNED DEFAULT 0 COMMENT 'model latest version', + `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'model current version', + `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'model last version', `audit_info` MEDIUMTEXT NOT NULL COMMENT 'model audit info', `deleted_at` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT 'model deleted at', PRIMARY KEY (`model_id`), diff --git a/scripts/mysql/upgrade-1.3.0-to-2.0.0-mysql.sql b/scripts/mysql/upgrade-1.3.0-to-2.0.0-mysql.sql index 6d4c22a17e..885301cd9c 100644 --- a/scripts/mysql/upgrade-1.3.0-to-2.0.0-mysql.sql +++ b/scripts/mysql/upgrade-1.3.0-to-2.0.0-mysql.sql @@ -26,3 +26,7 @@ ALTER TABLE `group_meta` CREATE UNIQUE INDEX `uk_mid_ueid_del` ON `user_meta` (`metalake_id`, `external_id`, `deleted_at`); CREATE UNIQUE INDEX `uk_mid_geid_del` ON `group_meta` (`metalake_id`, `external_id`, `deleted_at`); + +ALTER TABLE `model_meta` + ADD COLUMN `current_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'model current version' AFTER `model_latest_version`, + ADD COLUMN `last_version` INT UNSIGNED NOT NULL DEFAULT 1 COMMENT 'model last version' AFTER `current_version`; diff --git a/scripts/postgresql/schema-2.0.0-postgresql.sql b/scripts/postgresql/schema-2.0.0-postgresql.sql index b73f94cc5e..a51f60cbde 100644 --- a/scripts/postgresql/schema-2.0.0-postgresql.sql +++ b/scripts/postgresql/schema-2.0.0-postgresql.sql @@ -591,11 +591,15 @@ CREATE TABLE IF NOT EXISTS model_meta ( model_comment VARCHAR(65535) DEFAULT NULL, model_properties TEXT DEFAULT NULL, model_latest_version INT NOT NULL DEFAULT 0, + current_version INT NOT NULL DEFAULT 1, + last_version INT NOT NULL DEFAULT 1, audit_info TEXT NOT NULL, deleted_at BIGINT NOT NULL DEFAULT 0, PRIMARY KEY (model_id), UNIQUE (schema_id, model_name, deleted_at) ); +COMMENT ON COLUMN model_meta.current_version IS 'model current version'; +COMMENT ON COLUMN model_meta.last_version IS 'model last version'; CREATE INDEX IF NOT EXISTS model_meta_idx_metalake_id ON model_meta (metalake_id); CREATE INDEX IF NOT EXISTS model_meta_idx_catalog_id ON model_meta (catalog_id); diff --git a/scripts/postgresql/upgrade-1.3.0-to-2.0.0-postgresql.sql b/scripts/postgresql/upgrade-1.3.0-to-2.0.0-postgresql.sql index 0d781f2836..0d99fc24b3 100644 --- a/scripts/postgresql/upgrade-1.3.0-to-2.0.0-postgresql.sql +++ b/scripts/postgresql/upgrade-1.3.0-to-2.0.0-postgresql.sql @@ -28,3 +28,8 @@ COMMENT ON COLUMN group_meta.external_id IS 'external identifier from an upstrea CREATE UNIQUE INDEX IF NOT EXISTS uk_mid_ueid_del ON user_meta (metalake_id, external_id, deleted_at); CREATE UNIQUE INDEX IF NOT EXISTS uk_mid_geid_del ON group_meta (metalake_id, external_id, deleted_at); + +ALTER TABLE model_meta ADD COLUMN IF NOT EXISTS current_version INT NOT NULL DEFAULT 1; +ALTER TABLE model_meta ADD COLUMN IF NOT EXISTS last_version INT NOT NULL DEFAULT 1; +COMMENT ON COLUMN model_meta.current_version IS 'model current version'; +COMMENT ON COLUMN model_meta.last_version IS 'model last version';
