diff --git a/sqlglot/dialects/dialect.py b/sqlglot/dialects/dialect.py index 1522414e28..3ab40d673c 100644 --- a/sqlglot/dialects/dialect.py +++ b/sqlglot/dialects/dialect.py @@ -525,6 +525,11 @@ class Dialect(metaclass=_Dialect): Whether ORDER BY ALL is supported (expands to all the selected columns) as in DuckDB, Spark3/Databricks """ + SUPPORTS_LIMIT_ALL = False + """ + Whether LIMIT ALL is supported (equivalent to no limit) as in Postgres. + """ + PROJECTION_ALIASES_SHADOW_SOURCE_NAMES = False """ Whether projection alias names can shadow table/source names in GROUP BY and HAVING clauses. diff --git a/sqlglot/dialects/duckdb.py b/sqlglot/dialects/duckdb.py index 2d777fa512..bc4ed9bbec 100644 --- a/sqlglot/dialects/duckdb.py +++ b/sqlglot/dialects/duckdb.py @@ -21,6 +21,7 @@ class DuckDB(Dialect): CONCAT_COALESCE = True CONCAT_WS_COALESCE = True SUPPORTS_ORDER_BY_ALL = True + SUPPORTS_LIMIT_ALL = True SUPPORTS_FIXED_SIZE_ARRAYS = True STRICT_JSON_PATH_SYNTAX = False NUMBERS_CAN_BE_UNDERSCORE_SEPARATED = True diff --git a/sqlglot/dialects/postgres.py b/sqlglot/dialects/postgres.py index 111f6e393f..c79d256972 100644 --- a/sqlglot/dialects/postgres.py +++ b/sqlglot/dialects/postgres.py @@ -15,6 +15,7 @@ class Postgres(Dialect): CONCAT_COALESCE = True CONCAT_WS_COALESCE = True NULL_ORDERING = "nulls_are_large" + SUPPORTS_LIMIT_ALL = True TIME_FORMAT = "'YYYY-MM-DD HH24:MI:SS'" TABLESAMPLE_SIZE_IS_PERCENT = True TABLES_REFERENCEABLE_AS_COLUMNS = True diff --git a/sqlglot/dialects/presto.py b/sqlglot/dialects/presto.py index f086f3079a..2fea6e7585 100644 --- a/sqlglot/dialects/presto.py +++ b/sqlglot/dialects/presto.py @@ -22,6 +22,7 @@ class Presto(Dialect): TYPED_DIVISION = True TABLESAMPLE_SIZE_IS_PERCENT = True LOG_BASE_FIRST: bool | None = None + SUPPORTS_LIMIT_ALL = True SUPPORTS_VALUES_DEFAULT = False LEAST_GREATEST_IGNORES_NULLS = False UUID_IS_STRING_TYPE = False diff --git a/sqlglot/dialects/spark.py b/sqlglot/dialects/spark.py index 2ff76aa9a1..0cc9ef7d07 100644 --- a/sqlglot/dialects/spark.py +++ b/sqlglot/dialects/spark.py @@ -11,6 +11,7 @@ class Spark(Spark2): SUPPORTS_ORDER_BY_ALL = True + SUPPORTS_LIMIT_ALL = True SUPPORTS_NULL_TYPE = True ARRAY_FUNCS_PROPAGATES_NULLS = True EXPRESSION_METADATA = EXPRESSION_METADATA.copy() diff --git a/sqlglot/generators/sqlite.py b/sqlglot/generators/sqlite.py index b53f9369fe..a0a4cb1667 100644 --- a/sqlglot/generators/sqlite.py +++ b/sqlglot/generators/sqlite.py @@ -73,6 +73,18 @@ def _generated_to_auto_increment(expression: exp.Expr) -> exp.Expr: return expression +def _offset_to_limit(expression: exp.Expr) -> exp.Expr: + if not isinstance(expression, exp.Select): + return expression + + offset = expression.args.get("offset") + + if offset and not expression.args.get("limit"): + expression.limit(-1, copy=False) + + return expression + + class SQLiteGenerator(generator.Generator): SELECT_KINDS: tuple[str, ...] = () TRY_SUPPORTED = False @@ -152,6 +164,7 @@ class SQLiteGenerator(generator.Generator): exp.Rand: rename_func("RANDOM"), exp.Select: transforms.preprocess( [ + _offset_to_limit, transforms.eliminate_distinct_on, transforms.eliminate_qualify, transforms.eliminate_semi_and_anti_joins, @@ -182,6 +195,13 @@ class SQLiteGenerator(generator.Generator): LIMIT_FETCH = "LIMIT" + def insert_sql(self, expression: exp.Insert) -> str: + if expression.args.get("ignore"): + expression.set("ignore", False) + expression.set("alternative", "IGNORE") + + return super().insert_sql(expression) + def bitwiseandagg_sql(self, expression: exp.BitwiseAndAgg) -> str: self.unsupported("BITWISE_AND aggregation is not supported in SQLite") return self.function_fallback_sql(expression) diff --git a/sqlglot/parser.py b/sqlglot/parser.py index 84d84423ae..84fd50aa04 100644 --- a/sqlglot/parser.py +++ b/sqlglot/parser.py @@ -5488,6 +5488,9 @@ def _parse_limit( self._match_r_paren() else: + if self.dialect.SUPPORTS_LIMIT_ALL and self._match(TokenType.ALL): + return this + # Parsing LIMIT x% (i.e x PERCENT) as a term leads to an error, since # we try to build an exp.Mod expr. For that matter, we backtrack and instead # consume the factor plus parse the percentage separately diff --git a/tests/dialects/test_dialect.py b/tests/dialects/test_dialect.py index b4d63a4775..6a0abcae06 100644 --- a/tests/dialects/test_dialect.py +++ b/tests/dialects/test_dialect.py @@ -2747,6 +2747,18 @@ def test_safediv(self): ) def test_limit(self): + self.validate_identity("WITH t AS (SELECT 1 AS all) SELECT 1 FROM t LIMIT all") + self.validate_all( + "WITH t AS (SELECT 1 AS all) SELECT 1 FROM t", + read={ + "databricks": "WITH t AS (SELECT 1 AS all) SELECT 1 FROM t LIMIT all", + "duckdb": "WITH t AS (SELECT 1 AS all) SELECT 1 FROM t LIMIT all", + "presto": "WITH t AS (SELECT 1 AS all) SELECT 1 FROM t LIMIT all", + "postgres": "WITH t AS (SELECT 1 AS all) SELECT 1 FROM t LIMIT all", + "spark": "WITH t AS (SELECT 1 AS all) SELECT 1 FROM t LIMIT all", + }, + ) + self.validate_all( "SELECT * FROM data LIMIT 10, 20", write={"sqlite": "SELECT * FROM data LIMIT 20 OFFSET 10"}, diff --git a/tests/dialects/test_sqlite.py b/tests/dialects/test_sqlite.py index 3c090247cb..b0c7ebf6bf 100644 --- a/tests/dialects/test_sqlite.py +++ b/tests/dialects/test_sqlite.py @@ -97,6 +97,13 @@ def test_sqlite(self): "postgres": "SELECT JSON_OBJECT_AGG(name, value) FROM t", }, ) + self.validate_all( + "INSERT OR IGNORE INTO foo (x, y) VALUES (1, 2)", + read={ + "mysql": "INSERT IGNORE INTO foo (x, y) VALUES (1, 2)", + "sqlite": "INSERT OR IGNORE INTO foo (x, y) VALUES (1, 2)", + }, + ) self.validate_all( "CURRENT_DATE", read={