diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a1e2b5..71896cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # DLSync Changelog This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.6.0] - 2025-11-24 +### Added +- Added support Masking Policy object type +- Fixed log message unsupported object types in create script ## [2.5.0] - 2025-11-06 ### Added - Added support for Dynamic Table object type diff --git a/src/main/java/com/snowflake/dlsync/parser/SqlTokenizer.java b/src/main/java/com/snowflake/dlsync/parser/SqlTokenizer.java index 2bb934c..5aef267 100644 --- a/src/main/java/com/snowflake/dlsync/parser/SqlTokenizer.java +++ b/src/main/java/com/snowflake/dlsync/parser/SqlTokenizer.java @@ -29,7 +29,7 @@ public class SqlTokenizer { private static final String IDENTIFIER_REGEX = "((?:\\\"[^\"]+\\\"\\.)|(?:[{}$a-zA-Z0-9_]+\\.))?((?:\\\"[^\"]+\\\"\\.)|(?:[{}$a-zA-Z0-9_]+\\.))?(?i)"; private static final String MIGRATION_REGEX = VERSION_REGEX + AUTHOR_REGEX + CONTENT_REGEX + ROLL_BACK_REGEX + VERIFY_REGEX; - private static final String DDL_REGEX = ";\\n+(CREATE\\s+OR\\s+REPLACE\\s+(TRANSIENT\\s|HYBRID\\s|SECURE\\s)?(?DYNAMIC TABLE|FILE FORMAT|VIEW|FUNCTION|PROCEDURE|TABLE|STREAM|SEQUENCE|STAGE|TASK|STREAMLIT|PIPE|ALERT|\\w+)\\s+(?[\\\"\\w.]+)([\\s\\S]+?)(?=(;\\nCREATE\\s+)|(;$)))"; + private static final String DDL_REGEX = ";\\n+(CREATE\\s+OR\\s+REPLACE\\s+(TRANSIENT\\s|HYBRID\\s|SECURE\\s)?(?DYNAMIC TABLE|FILE FORMAT|MASKING POLICY|VIEW|FUNCTION|PROCEDURE|TABLE|STREAM|SEQUENCE|STAGE|TASK|STREAMLIT|PIPE|ALERT|\\w+)\\s+(?[\\\"\\w.]+)([\\s\\S]+?)(?=(;\\nCREATE\\s+)|(;$)))"; private static final String STRING_LITERAL_REGEX = "(? ot.getSingular().equalsIgnoreCase(type)) - .collect(Collectors.toList()).get(0); + Optional optionalObjectType = Arrays.stream(ScriptObjectType.values()).filter( ot -> ot.getSingular().equalsIgnoreCase(type)).findFirst(); + if(!optionalObjectType.isPresent()) { + log.error("Unsupported object type: {} found in DDL!", type); + throw new RuntimeException("Unknown object type found in DDL: " + type); + } + ScriptObjectType objectType = optionalObjectType.get(); String fullObjectName = matcher.group("name"); String scriptObjectName = fullObjectName.split("\\.")[2]; diff --git a/src/test/java/com/snowflake/dlsync/parser/SqlTokenizerTest.java b/src/test/java/com/snowflake/dlsync/parser/SqlTokenizerTest.java index f2bf1c1..f831c0a 100644 --- a/src/test/java/com/snowflake/dlsync/parser/SqlTokenizerTest.java +++ b/src/test/java/com/snowflake/dlsync/parser/SqlTokenizerTest.java @@ -202,6 +202,8 @@ class SqlTokenizerTest { "create or replace transient table db1.schema1.table2 (col1 varchar, col2 number);\n" + "create or replace hybrid table db1.schema1.table3 (col1 varchar, col2 number);\n" + "create or replace table db1.schema1.\"table4\" (col1 varchar, col2 number);\n" + + "create or replace dynamic table db1.schema1.dynamic_table1 (col1 varchar, col2 number)\n as SELECT id, name, COUNT(*) as count FROM db1.schema1.source_table GROUP BY id, name;\n" + + "create or replace masking policy db1.schema1.masking_policy1 as (val string) returns string -> case when current_role() in ('ANALYST_ROLE', 'PUBLIC') then val else '****' end;\n" + "create or replace function db1.schema1.function1(arg1 varchar)\n" + "RETURNS VARCHAR(16777216)\n" + "LANGUAGE JAVASCRIPT\n" + @@ -215,6 +217,8 @@ class SqlTokenizerTest { ScriptFactory.getMigrationScript("db1", "schema1", ScriptObjectType.TABLES, "table2","create or replace transient table db1.schema1.table2 (col1 varchar, col2 number);"), ScriptFactory.getMigrationScript("db1", "schema1", ScriptObjectType.TABLES, "table3","create or replace hybrid table db1.schema1.table3 (col1 varchar, col2 number);"), ScriptFactory.getMigrationScript("db1", "schema1", ScriptObjectType.TABLES, "\"table4\"","create or replace table db1.schema1.\"table4\" (col1 varchar, col2 number);"), + ScriptFactory.getMigrationScript("db1", "schema1", ScriptObjectType.DYNAMIC_TABLES, "dynamic_table1","create or replace dynamic table db1.schema1.dynamic_table1 (col1 varchar, col2 number)\n as SELECT id, name, COUNT(*) as count FROM db1.schema1.source_table GROUP BY id, name;"), + ScriptFactory.getStateScript("db1", "schema1", ScriptObjectType.MASKING_POLICIES, "masking_policy1","create or replace masking policy db1.schema1.masking_policy1 as (val string) returns string -> case when current_role() in ('ANALYST_ROLE', 'PUBLIC') then val else '****' end;"), ScriptFactory.getStateScript("db1", "schema1", ScriptObjectType.FUNCTIONS, "function1","create or replace function db1.schema1.function1(arg1 varchar)\n" + "RETURNS VARCHAR(16777216)\n" + "LANGUAGE JAVASCRIPT\n" + @@ -621,4 +625,16 @@ class SqlTokenizerTest { "Exception message should indicate unknown script type"); } + @Test + void parseDdlScriptUnsupportedObjectType() { + String ddl = "create or replace schema schema1;\n\nCREATE OR REPLACE UNKNOWN db1.schema1.OBJECT1;"; + + RuntimeException exception = assertThrows(RuntimeException.class, () -> { + SqlTokenizer.parseDdlScripts(ddl, "db1", "schema1"); + }, "Should throw RuntimeException for unsupported object type"); + + assertEquals("Unknown object type found in DDL: UNKNOWN", exception.getMessage(), + "Exception message should indicate unsupported DDL statement"); + } + } \ No newline at end of file