Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions sqlglot/dialects/dialect.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
1 change: 1 addition & 0 deletions sqlglot/dialects/postgres.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
20 changes: 20 additions & 0 deletions sqlglot/generators/sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: let's gate any logic in this transformation with an instance check:

if isinstance(expr, exp.Select):
    ...

return 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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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")

Comment on lines +199 to +202
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... this is fine for now, but looks like a code smell to me. Having two ways to represent the IGNORE semantics doesn't look right. Perhaps something to clean up in the future...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch here. let me think about a better way to do this

Copy link
Copy Markdown
Collaborator

@georgesittas georgesittas May 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for now, let's not deal with it in this PR. Just wanted to leave a note for future reference.

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)
Expand Down
3 changes: 3 additions & 0 deletions sqlglot/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions tests/dialects/test_dialect.py
Original file line number Diff line number Diff line change
Expand Up @@ -2747,6 +2747,17 @@ def test_safediv(self):
)

def test_limit(self):
limit_all_identifier = "WITH t AS (SELECT 1 AS all) SELECT 1 FROM t LIMIT all"
self.assertEqual(parse_one(limit_all_identifier).sql(), limit_all_identifier)
self.assertEqual(
parse_one(limit_all_identifier, read="postgres").sql("postgres"),
"WITH t AS (SELECT 1 AS all) SELECT 1 FROM t",
)
self.assertEqual(
parse_one(limit_all_identifier, read="mysql").sql("mysql"),
"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"},
Expand Down
37 changes: 37 additions & 0 deletions tests/dialects/test_sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,43 @@ 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(
"SELECT x FROM y",
read={
"postgres": "SELECT x FROM y LIMIT ALL",
},
write={
"postgres": "SELECT x FROM y",
},
)
self.validate_all(
"SELECT x FROM y LIMIT -1 OFFSET 10",
read={
"postgres": "SELECT x FROM y OFFSET 10",
},
write={
"postgres": "SELECT x FROM y LIMIT -1 OFFSET 10",
},
)
self.validate_all(
"SELECT x FROM y LIMIT -1 OFFSET 10",
read={
"postgres": "SELECT x FROM y LIMIT ALL OFFSET 10",
},
)
self.validate_all(
'SELECT x FROM y LIMIT "all"',
read={
"postgres": 'SELECT x FROM y LIMIT "all"',
},
)
self.validate_all(
"CURRENT_DATE",
read={
Expand Down
Loading