diff --git a/CHANGELOG.txt b/CHANGELOG.txt index ea8f2da..3f5623f 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,13 @@ +**v0.25.0** +## Fixes: + +1. Fix for issue with 'at time zone' https://github.com/xnuinside/simple-ddl-parser/issues/112 + +## New features: + +1. Added flag to raise errors if parser cannot parse statement DDLParser(.., silent=False) - https://github.com/xnuinside/simple-ddl-parser/issues/109 + + **v0.24.2** ## Fixes: diff --git a/README.md b/README.md index 2041799..0492ea4 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,15 @@ To dump result in json use argument .run(dump=True) You also can provide a path where you want to have a dumps with schema with argument .run(dump_path='folder_that_use_for_dumps/') + +### Raise error if DDL cannot be parsed by Parser + +By default Parser does not raise the error if some statement cannot be parsed - and just skip & produce empty output. + +To change this behavior you can pass 'silent=False' argumen to main parser class, like: + + DDLParser(.., silent=False) + ## Supported Statements - CREATE TABLE [ IF NOT EXISTS ] + columns defenition, columns attributes: column name + type + type size(for example, varchar(255)), UNIQUE, PRIMARY KEY, DEFAULT, CHECK, NULL/NOT NULL, REFERENCES, ON DELETE, ON UPDATE, NOT DEFERRABLE, DEFERRABLE INITIALLY, GENERATED ALWAYS, STORED, COLLATE @@ -393,6 +402,16 @@ https://github.com/swiatek25 ## Changelog +**v0.25.0** +## Fixes: + +1. Fix for issue with 'at time zone' https://github.com/xnuinside/simple-ddl-parser/issues/112 + +## New features: + +1. Added flag to raise errors if parser cannot parse statement DDLParser(.., silent=False) - https://github.com/xnuinside/simple-ddl-parser/issues/109 + + **v0.24.2** ## Fixes: diff --git a/docs/README.rst b/docs/README.rst index 6348731..1a6320a 100644 --- a/docs/README.rst +++ b/docs/README.rst @@ -286,6 +286,18 @@ To dump result in json use argument .run(dump=True) You also can provide a path where you want to have a dumps with schema with argument .run(dump_path='folder_that_use_for_dumps/') +Raise error if DDL cannot be parsed by Parser +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default Parser does not raise the error if some statement cannot be parsed - and just skip & produce empty output. + +To change this behavior you can pass 'silent=False' argumen to main parser class, like: + +.. code-block:: + + DDLParser(.., silent=False) + + Supported Statements -------------------- @@ -453,6 +465,20 @@ https://github.com/swiatek25 Changelog --------- +**v0.25.0** + +Fixes: +------ + + +#. Fix for issue with 'at time zone' https://github.com/xnuinside/simple-ddl-parser/issues/112 + +New features: +------------- + + +#. Added flag to raise errors if parser cannot parse statement DDLParser(.., silent=False) - https://github.com/xnuinside/simple-ddl-parser/issues/109 + **v0.24.2** Fixes: diff --git a/pyproject.toml b/pyproject.toml index 63f79fc..7d797d4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "simple-ddl-parser" -version = "0.24.2" +version = "0.25.0" description = "Simple DDL Parser to parse SQL & dialects like HQL, TSQL (MSSQL), Oracle, AWS Redshift, Snowflake, MySQL, PostgreSQL, etc ddl files to json/python dict with full information about columns: types, defaults, primary keys, etc.; sequences, alters, custom types & other entities from ddl." authors = ["Iuliia Volkova "] license = "MIT" diff --git a/simple_ddl_parser/__init__.py b/simple_ddl_parser/__init__.py index eeeee6f..b14cdf2 100644 --- a/simple_ddl_parser/__init__.py +++ b/simple_ddl_parser/__init__.py @@ -1,3 +1,4 @@ -from simple_ddl_parser.ddl_parser import DDLParser, parse_from_file +from simple_ddl_parser.ddl_parser import (DDLParser, DDLParserError, + parse_from_file) -__all__ = ["DDLParser", "parse_from_file"] +__all__ = ["DDLParser", "parse_from_file", "DDLParserError"] diff --git a/simple_ddl_parser/ddl_parser.py b/simple_ddl_parser/ddl_parser.py index 15b7769..4fc7824 100755 --- a/simple_ddl_parser/ddl_parser.py +++ b/simple_ddl_parser/ddl_parser.py @@ -12,6 +12,10 @@ from simple_ddl_parser.parser import Parser +class DDLParserError(Exception): + pass + + class DDLParser( Parser, Snowflake, BaseSQL, HQL, MySQL, MSSQL, Oracle, Redshift, BigQuery ): @@ -172,7 +176,6 @@ def set_lexx_tags(self, t): def set_last_token(self, t): self.lexer.last_token = t.type - return t def p_id(self, p): @@ -182,10 +185,11 @@ def p_id(self, p): p[0] = p[1] def t_error(self, t): - raise SyntaxError("Unknown symbol %r" % (t.value[0],)) + raise DDLParserError("Unknown symbol %r" % (t.value[0],)) def p_error(self, p): - pass + if not self.silent: + raise DDLParserError(f"Unknown statement at {p}") def parse_from_file(file_path: str, **kwargs) -> List[Dict]: diff --git a/simple_ddl_parser/dialects/sql.py b/simple_ddl_parser/dialects/sql.py index dd5d014..fcbd3aa 100644 --- a/simple_ddl_parser/dialects/sql.py +++ b/simple_ddl_parser/dialects/sql.py @@ -1056,14 +1056,18 @@ def p_default(self, p: List) -> None: | default FOR dot_id | DEFAULT funct_expr | DEFAULT LP pid RP + | DEFAULT LP funct_expr pid RP | default id | default LP RP """ - p_list = list(p) + p_list = remove_par(list(p)) default = self.pre_process_default(p_list) - if not isinstance(default, dict) and default.isnumeric(): + if isinstance(p_list[-1], list): + p_list[-1] = " ".join(p_list[-1]) + default = " ".join(p_list[1:]) + elif not isinstance(default, dict) and default.isnumeric(): default = int(default) if isinstance(p[1], dict): diff --git a/simple_ddl_parser/parser.py b/simple_ddl_parser/parser.py index 0d8e669..54c9529 100755 --- a/simple_ddl_parser/parser.py +++ b/simple_ddl_parser/parser.py @@ -26,9 +26,10 @@ class Parser: Subclass must include tokens for parser and rules """ - def __init__(self, content: str) -> None: + def __init__(self, content: str, silent: bool = True) -> None: """init parser for file""" self.tables = [] + self.silent = silent self.data = content.encode("unicode_escape") self.paren_count = 0 self.lexer = lex.lex(object=self, debug=False) diff --git a/tests/non_statement_tests/test_common.py b/tests/non_statement_tests/test_common.py index 9ce6b6e..2444e52 100644 --- a/tests/non_statement_tests/test_common.py +++ b/tests/non_statement_tests/test_common.py @@ -1,4 +1,6 @@ -from simple_ddl_parser import DDLParser +import pytest + +from simple_ddl_parser import DDLParser, DDLParserError def test_no_unexpected_logs(capsys): @@ -18,3 +20,16 @@ def test_no_unexpected_logs(capsys): out, err = capsys.readouterr() assert out == "" assert err == "" + + +def test_silent_false_flag(): + ddl = """ +CREATE TABLE foo + ( + created_timestamp TIMESTAMPTZ NOT NULL DEFAULT ALTER (now() at time zone 'utc') + ); +""" + with pytest.raises(DDLParserError) as e: + DDLParser(ddl, silent=False).run(group_by_type=True) + + assert "Unknown statement" in e.value[1] diff --git a/tests/test_simple_ddl_parser.py b/tests/test_simple_ddl_parser.py index 60ffd54..ea2d68b 100644 --- a/tests/test_simple_ddl_parser.py +++ b/tests/test_simple_ddl_parser.py @@ -2496,3 +2496,67 @@ def test_table_name_reserved_word_after_dot(): "types": [], } assert expected == result + + +def test_add_timezone(): + ddl = """ +CREATE TABLE foo + ( + bar_timestamp bigint DEFAULT 1002 * extract(epoch from now()) * 1000, + bar_timestamp2 bigint DEFAULT (1002 * extract(epoch from now()) * 1000), + created_timestamp TIMESTAMPTZ NOT NULL DEFAULT (now() at time zone 'utc') + ); + """ + result = DDLParser(ddl).run(group_by_type=True) + expected = { + "ddl_properties": [], + "domains": [], + "schemas": [], + "sequences": [], + "tables": [ + { + "alter": {}, + "checks": [], + "columns": [ + { + "check": None, + "default": "1002 * extract(epoch from now()) * 1000", + "name": "bar_timestamp", + "nullable": True, + "references": None, + "size": None, + "type": "bigint", + "unique": False, + }, + { + "check": None, + "default": "1002 * extract(epoch from now()) * 1000", + "name": "bar_timestamp2", + "nullable": True, + "references": None, + "size": None, + "type": "bigint", + "unique": False, + }, + { + "check": None, + "default": "DEFAULT now() at time zone 'utc'", + "name": "created_timestamp", + "nullable": False, + "references": None, + "size": None, + "type": "TIMESTAMPTZ", + "unique": False, + }, + ], + "index": [], + "partitioned_by": [], + "primary_key": [], + "schema": None, + "table_name": "foo", + "tablespace": None, + } + ], + "types": [], + } + assert expected == result