SQLGlot عبارة عن محلل SQL ومحول ومحسن ومحرك بدون تبعية. ويمكن استخدامه لتنسيق SQL أو الترجمة بين 24 لهجة مختلفة مثل DuckDB وPresto / Trino وSpark / Databricks وSnowflake وBigQuery. ويهدف إلى قراءة مجموعة واسعة من مدخلات SQL وإخراج SQL الصحيح من الناحية النحوية والدلالية في اللهجات المستهدفة.
إنه محلل SQL عام وشامل جدًا مع مجموعة اختبار قوية. كما أنها فعالة جدًا، بينما تتم كتابتها بلغة بايثون فقط.
يمكنك بسهولة تخصيص المحلل اللغوي وتحليل الاستعلامات واجتياز أشجار التعبير وإنشاء SQL برمجيًا.
يتم تمييز الأخطاء النحوية ويمكن أن يؤدي عدم توافق اللهجات إلى التحذير أو الزيادة وفقًا للتكوينات. ومع ذلك، لا يهدف SQLGlot إلى أن يكون مدققًا لـ SQL، لذلك قد يفشل في اكتشاف بعض الأخطاء النحوية.
تعرف على المزيد حول SQLGlot في وثائق واجهة برمجة التطبيقات (API) والكتاب التمهيدي لشجرة التعبير.
المساهمات موضع ترحيب كبير في SQLGlot؛ اقرأ دليل المساهمة ووثيقة الإعداد للبدء!
من باي بي آي:
pip3 install " sqlglot[rs] "
# Without Rust tokenizer (slower):
# pip3 install sqlglot
أو مع الخروج المحلي:
make install
متطلبات التطوير (اختياري):
make install-dev
نظرا لرقم الإصدار MAJOR
. MINOR
. PATCH
، يستخدم SQLGlot استراتيجية الإصدار التالية:
PATCH
عندما تكون هناك إصلاحات أو إضافات ميزات متوافقة مع الإصدارات السابقة.MINOR
عندما تكون هناك إصلاحات أو إضافات ميزات غير متوافقة مع الإصدارات السابقة.MAJOR
عندما تكون هناك إصلاحات كبيرة غير متوافقة مع الإصدارات السابقة أو إضافات الميزات. نحن نحب أن نسمع منك. انضم إلى قناة Slack لمجتمعنا!
لقد حاولت تحليل SQL الذي يجب أن يكون صالحًا لكنه فشل، لماذا حدث ذلك؟
parse_one(sql, dialect="spark")
(بدلاً من ذلك: read="spark"
). إذا لم يتم تحديد لهجة، فسيحاول parse_one
تحليل الاستعلام وفقًا لـ "لهجة SQLGlot"، والتي تم تصميمها لتكون مجموعة شاملة من جميع اللهجات المدعومة. إذا حاولت تحديد اللهجة وما زالت لا تعمل، فيرجى تقديم مشكلة.حاولت إخراج SQL ولكنها ليست باللهجة الصحيحة!
parse_one(sql, dialect="spark").sql(dialect="duckdb")
(بدلاً من ذلك: transpile(sql, read="spark", write="duckdb")
).لقد حاولت تحليل SQL غير صالح وقد نجح الأمر، على الرغم من أنه قد يؤدي إلى حدوث خطأ! لماذا لم يتم التحقق من صحة SQL الخاص بي؟
ماذا حدث لsqlglot.dataframe؟
ترجمة بسهولة من لهجة إلى أخرى. على سبيل المثال، تختلف وظائف التاريخ/الوقت بين اللهجات وقد يكون من الصعب التعامل معها:
import sqlglot
sqlglot . transpile ( "SELECT EPOCH_MS(1618088028295)" , read = "duckdb" , write = "hive" )[ 0 ]
' SELECT FROM_UNIXTIME(1618088028295 / POW(10, 3)) '
يمكن لـ SQLGlot أيضًا ترجمة تنسيقات الوقت المخصصة:
import sqlglot
sqlglot . transpile ( "SELECT STRFTIME(x, '%y-%-m-%S')" , read = "duckdb" , write = "hive" )[ 0 ]
" SELECT DATE_FORMAT(x, 'yy-M-ss') "
يمكن أيضًا ترجمة محددات المعرفات وأنواع البيانات:
import sqlglot
# Spark SQL requires backticks (`) for delimited identifiers and uses `FLOAT` over `REAL`
sql = """WITH baz AS (SELECT a, c FROM foo WHERE a = 1) SELECT f.a, b.b, baz.c, CAST("b"."a" AS REAL) d FROM foo f JOIN bar b ON f.a = b.a LEFT JOIN baz ON f.a = baz.a"""
# Translates the query into Spark SQL, formats it, and delimits all of its identifiers
print ( sqlglot . transpile ( sql , write = "spark" , identify = True , pretty = True )[ 0 ])
WITH ` baz ` AS (
SELECT
` a ` ,
` c `
FROM ` foo `
WHERE
` a ` = 1
)
SELECT
` f ` . ` a ` ,
` b ` . ` b ` ,
` baz ` . ` c ` ,
CAST( ` b ` . ` a ` AS FLOAT) AS ` d `
FROM ` foo ` AS ` f `
JOIN ` bar ` AS ` b `
ON ` f ` . ` a ` = ` b ` . ` a `
LEFT JOIN ` baz `
ON ` f ` . ` a ` = ` baz ` . ` a `
يتم أيضًا الاحتفاظ بالتعليقات على أساس أفضل جهد:
sql = """
/* multi
line
comment
*/
SELECT
tbl.cola /* comment 1 */ + tbl.colb /* comment 2 */,
CAST(x AS SIGNED), # comment 3
y -- comment 4
FROM
bar /* comment 5 */,
tbl # comment 6
"""
# Note: MySQL-specific comments (`#`) are converted into standard syntax
print ( sqlglot . transpile ( sql , read = 'mysql' , pretty = True )[ 0 ])
/* multi
line
comment
*/
SELECT
tbl . cola /* comment 1 */ + tbl . colb /* comment 2 */ ,
CAST(x AS INT ), /* comment 3 */
y /* comment 4 */
FROM bar /* comment 5 */ , tbl /* comment 6 */
يمكنك استكشاف SQL باستخدام مساعدي التعبير للقيام بأشياء مثل البحث عن الأعمدة والجداول في الاستعلام:
from sqlglot import parse_one , exp
# print all column references (a and b)
for column in parse_one ( "SELECT a, b + 1 AS c FROM d" ). find_all ( exp . Column ):
print ( column . alias_or_name )
# find all projections in select statements (a and c)
for select in parse_one ( "SELECT a, b + 1 AS c FROM d" ). find_all ( exp . Select ):
for projection in select . expressions :
print ( projection . alias_or_name )
# find all tables (x, y, z)
for table in parse_one ( "SELECT * FROM x JOIN y JOIN z" ). find_all ( exp . Table ):
print ( table . name )
اقرأ الكتاب التمهيدي لمعرفة المزيد حول الأجزاء الداخلية لـ SQLGlot.
عندما يكتشف المحلل وجود خطأ في بناء الجملة، فإنه يطلق خطأ ParseError
:
import sqlglot
sqlglot . transpile ( "SELECT foo FROM (SELECT baz FROM t" )
sqlglot.errors.ParseError: Expecting ). Line 1, Col: 34.
SELECT foo FROM (SELECT baz FROM t
~
يمكن الوصول إلى أخطاء بناء الجملة المنظمة للاستخدام البرمجي:
import sqlglot
try :
sqlglot . transpile ( "SELECT foo FROM (SELECT baz FROM t" )
except sqlglot . errors . ParseError as e :
print ( e . errors )
[{
'description' : 'Expecting )' ,
'line' : 1 ,
'col' : 34 ,
'start_context' : 'SELECT foo FROM (SELECT baz FROM ' ,
'highlight' : 't' ,
'end_context' : '' ,
'into_expression' : None
}]
قد لا يكون من الممكن ترجمة بعض الاستفسارات بين لهجات معينة. في هذه الحالات، قد يصدر SQLGlot تحذيرًا وسيستمر في إجراء ترجمة بأفضل جهد افتراضيًا:
import sqlglot
sqlglot . transpile ( "SELECT APPROX_DISTINCT(a, 0.1) FROM foo" , read = "presto" , write = "hive" )
APPROX_COUNT_DISTINCT does not support accuracy
' SELECT APPROX_COUNT_DISTINCT(a) FROM foo '
يمكن تغيير هذا السلوك عن طريق تعيين السمة unsupported_level
. على سبيل المثال، يمكننا ضبطه إما على RAISE
أو IMMEDIATE
لضمان ظهور استثناء بدلاً من ذلك:
import sqlglot
sqlglot . transpile ( "SELECT APPROX_DISTINCT(a, 0.1) FROM foo" , read = "presto" , write = "hive" , unsupported_level = sqlglot . ErrorLevel . RAISE )
sqlglot.errors.UnsupportedError: APPROX_COUNT_DISTINCT does not support accuracy
هناك استعلامات تتطلب معلومات إضافية ليتم نقلها بدقة، مثل مخططات الجداول المشار إليها فيها. وذلك لأن بعض التحويلات حساسة للنوع، مما يعني أن استنتاج الكتابة ضروري لفهم دلالاتها. على الرغم من أن قواعد المُحسِّن qualify
و annotate_types
يمكن أن تساعد في ذلك، إلا أنه لا يتم استخدامها افتراضيًا لأنها تضيف حملًا وتعقيدًا كبيرًا.
تعد عملية النقل مشكلة صعبة بشكل عام، لذا يستخدم SQLGlot أسلوبًا "تزايديًا" لحلها. وهذا يعني أنه قد تكون هناك أزواج لهجات تفتقر حاليًا إلى الدعم لبعض المدخلات، ولكن من المتوقع أن يتحسن هذا الأمر بمرور الوقت. نحن نقدر بشدة المشكلات أو العلاقات العامة الموثقة والمختبرة جيدًا، لذا لا تتردد في التواصل معنا إذا كنت بحاجة إلى إرشادات!
يدعم SQLGlot إنشاء تعبيرات SQL بشكل متزايد:
from sqlglot import select , condition
where = condition ( "x=1" ). and_ ( "y=1" )
select ( "*" ). from_ ( "y" ). where ( where ). sql ()
' SELECT * FROM y WHERE x = 1 AND y = 1 '
من الممكن تعديل شجرة تم تحليلها:
from sqlglot import parse_one
parse_one ( "SELECT x FROM y" ). from_ ( "z" ). sql ()
' SELECT x FROM z '
يمكن أيضًا تحويل التعبيرات المحللة بشكل متكرر عن طريق تطبيق وظيفة التعيين على كل عقدة في الشجرة:
from sqlglot import exp , parse_one
expression_tree = parse_one ( "SELECT a FROM x" )
def transformer ( node ):
if isinstance ( node , exp . Column ) and node . name == "a" :
return parse_one ( "FUN(a)" )
return node
transformed_tree = expression_tree . transform ( transformer )
transformed_tree . sql ()
' SELECT FUN(a) FROM x '
يمكن لـ SQLGlot إعادة كتابة الاستعلامات في نموذج "مُحسّن". يقوم بتنفيذ مجموعة متنوعة من التقنيات لإنشاء AST أساسي جديد. يمكن استخدام AST لتوحيد الاستعلامات أو توفير الأسس اللازمة لتنفيذ محرك فعلي. على سبيل المثال:
import sqlglot
from sqlglot . optimizer import optimize
print (
optimize (
sqlglot . parse_one ( """
SELECT A OR (B OR (C AND D))
FROM x
WHERE Z = date '2021-01-01' + INTERVAL '1' month OR 1 = 0
""" ),
schema = { "x" : { "A" : "INT" , "B" : "INT" , "C" : "INT" , "D" : "INT" , "Z" : "STRING" }}
). sql ( pretty = True )
)
SELECT
(
" x " . " a " <> 0 OR " x " . " b " <> 0 OR " x " . " c " <> 0
)
AND (
" x " . " a " <> 0 OR " x " . " b " <> 0 OR " x " . " d " <> 0
) AS " _col_0 "
FROM " x " AS " x "
WHERE
CAST( " x " . " z " AS DATE ) = CAST( ' 2021-02-01 ' AS DATE )
يمكنك رؤية إصدار AST من SQL الذي تم تحليله عن طريق استدعاء repr
:
from sqlglot import parse_one
print ( repr ( parse_one ( "SELECT a + 1 AS z" )))
Select (
expressions = [
Alias (
this = Add (
this = Column (
this = Identifier ( this = a , quoted = False )),
expression = Literal ( this = 1 , is_string = False )),
alias = Identifier ( this = z , quoted = False ))])
يمكن لـ SQLGlot حساب الفرق الدلالي بين تعبيرين وتغييرات الإخراج في شكل سلسلة من الإجراءات اللازمة لتحويل تعبير المصدر إلى تعبير مستهدف:
from sqlglot import diff , parse_one
diff ( parse_one ( "SELECT a + b, c, d" ), parse_one ( "SELECT c, a - b, d" ))
[
Remove ( expression = Add (
this = Column (
this = Identifier ( this = a , quoted = False )),
expression = Column (
this = Identifier ( this = b , quoted = False )))),
Insert ( expression = Sub (
this = Column (
this = Identifier ( this = a , quoted = False )),
expression = Column (
this = Identifier ( this = b , quoted = False )))),
Keep (
source = Column ( this = Identifier ( this = a , quoted = False )),
target = Column ( this = Identifier ( this = a , quoted = False ))),
...
]
أنظر أيضا: الفرق الدلالي لـ SQL.
يمكن إضافة اللهجات عن طريق تصنيف Dialect
الفرعية:
from sqlglot import exp
from sqlglot . dialects . dialect import Dialect
from sqlglot . generator import Generator
from sqlglot . tokens import Tokenizer , TokenType
class Custom ( Dialect ):
class Tokenizer ( Tokenizer ):
QUOTES = [ "'" , '"' ]
IDENTIFIERS = [ "`" ]
KEYWORDS = {
** Tokenizer . KEYWORDS ,
"INT64" : TokenType . BIGINT ,
"FLOAT64" : TokenType . DOUBLE ,
}
class Generator ( Generator ):
TRANSFORMS = { exp . Array : lambda self , e : f"[ { self . expressions ( e ) } ]" }
TYPE_MAPPING = {
exp . DataType . Type . TINYINT : "INT64" ,
exp . DataType . Type . SMALLINT : "INT64" ,
exp . DataType . Type . INT : "INT64" ,
exp . DataType . Type . BIGINT : "INT64" ,
exp . DataType . Type . DECIMAL : "NUMERIC" ,
exp . DataType . Type . FLOAT : "FLOAT64" ,
exp . DataType . Type . DOUBLE : "FLOAT64" ,
exp . DataType . Type . BOOLEAN : "BOOL" ,
exp . DataType . Type . TEXT : "STRING" ,
}
print ( Dialect [ "custom" ])
<class '__main__.Custom'>
SQLGlot قادر على تفسير استعلامات SQL، حيث يتم تمثيل الجداول كقواميس Python. ليس من المفترض أن يكون المحرك سريعًا، لكنه قد يكون مفيدًا لاختبار الوحدات وتشغيل SQL محليًا عبر كائنات Python. بالإضافة إلى ذلك، يمكن دمج الأساس بسهولة مع نوى الحوسبة السريعة، مثل Arrow وPandas.
يوضح المثال أدناه تنفيذ استعلام يتضمن التجميعات والصلات:
from sqlglot . executor import execute
tables = {
"sushi" : [
{ "id" : 1 , "price" : 1.0 },
{ "id" : 2 , "price" : 2.0 },
{ "id" : 3 , "price" : 3.0 },
],
"order_items" : [
{ "sushi_id" : 1 , "order_id" : 1 },
{ "sushi_id" : 1 , "order_id" : 1 },
{ "sushi_id" : 2 , "order_id" : 1 },
{ "sushi_id" : 3 , "order_id" : 2 },
],
"orders" : [
{ "id" : 1 , "user_id" : 1 },
{ "id" : 2 , "user_id" : 2 },
],
}
execute (
"""
SELECT
o.user_id,
SUM(s.price) AS price
FROM orders o
JOIN order_items i
ON o.id = i.order_id
JOIN sushi s
ON i.sushi_id = s.id
GROUP BY o.user_id
""" ,
tables = tables
)
user_id price
1 4.0
2 3.0
راجع أيضًا: كتابة محرك Python SQL من البداية.
يستخدم SQLGlot pdoc لخدمة وثائق API الخاصة به.
يوجد إصدار مستضاف على موقع SQLGlot، أو يمكنك إنشاءه محليًا باستخدام:
make docs-serve
make style # Only linter checks
make unit # Only unit tests (or unit-rs, to use the Rust tokenizer)
make test # Unit and integration tests (or test-rs, to use the Rust tokenizer)
make check # Full test suite & linter checks
تعمل المعايير على Python 3.10.12 في ثوانٍ.
استفسار | com.sqlglot | com.sqlglotrs | com.sqlfluff | com.sqltree | com.sqlparse | moz_sql_parser | com.sqloxide |
---|---|---|---|---|---|---|---|
tpch | 0.00944 (1.0) | 0.00590 (0.625) | 0.32116 (33.98) | 0.00693 (0.734) | 0.02858 (3.025) | 0.03337 (3.532) | 0.00073 (0.077) |
قصير | 0.00065 (1.0) | 0.00044 (0.687) | 0.03511 (53.82) | 0.00049 (0.759) | 0.00163 (2.506) | 0.00234 (3.601) | 0.00005 (0.073) |
طويل | 0.00889 (1.0) | 0.00572 (0.643) | 0.36982 (41.56) | 0.00614 (0.690) | 0.02530 (2.844) | 0.02931 (3.294) | 0.00059 (0.066) |
مجنون | 0.02918 (1.0) | 0.01991 (0.682) | 1.88695 (64.66) | 0.02003 (0.686) | 7.46894 (255.9) | 0.64994 (22.27) | 0.00327 (0.112) |
يستخدم SQLGlot dateutil لتبسيط تعبيرات timedelta الحرفية. لن يقوم المحسن بتبسيط التعبيرات مثل ما يلي إذا تعذر العثور على الوحدة النمطية:
x + interval ' 1 ' month