ddsql 0.0.2__tar.gz → 0.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ddsql-0.0.2 → ddsql-0.0.3}/PKG-INFO +15 -1
- {ddsql-0.0.2 → ddsql-0.0.3}/README.md +14 -0
- {ddsql-0.0.2 → ddsql-0.0.3}/ddsql/serializers/base.py +16 -11
- ddsql-0.0.3/ddsql/serializers/clickhouse.py +50 -0
- ddsql-0.0.3/ddsql/serializers/postgres.py +62 -0
- {ddsql-0.0.2 → ddsql-0.0.3}/pyproject.toml +1 -1
- ddsql-0.0.2/ddsql/serializers/clickhouse.py +0 -21
- ddsql-0.0.2/ddsql/serializers/postgres.py +0 -21
- {ddsql-0.0.2 → ddsql-0.0.3}/LICENSE +0 -0
- {ddsql-0.0.2 → ddsql-0.0.3}/ddsql/__init__.py +0 -0
- {ddsql-0.0.2 → ddsql-0.0.3}/ddsql/adapter.py +0 -0
- {ddsql-0.0.2 → ddsql-0.0.3}/ddsql/py.typed +0 -0
- {ddsql-0.0.2 → ddsql-0.0.3}/ddsql/query.py +0 -0
- {ddsql-0.0.2 → ddsql-0.0.3}/ddsql/serializers/__init__.py +0 -0
- {ddsql-0.0.2 → ddsql-0.0.3}/ddsql/sqlbase.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ddsql
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.3
|
|
4
4
|
Summary: SQL Query Library
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -58,6 +58,20 @@ or create your own by inheriting from `BaseSerializer`.
|
|
|
58
58
|
| `date` | `'2025-01-01'` | `'2025-01-01'::date` | `toDate('2025-01-01')` |
|
|
59
59
|
| `list`/`tuple`/`set` | `(item1, item2, ...)` | `(item1, item2, ...)` | `(item1, item2, ...)` |
|
|
60
60
|
|
|
61
|
+
String values are escaped according to the dialect rules, so quotes, backslashes
|
|
62
|
+
and control characters cannot break the query:
|
|
63
|
+
|
|
64
|
+
- `BaseSerializer` doubles single quotes (`O'Brien` → `'O''Brien'`), as defined by the SQL standard.
|
|
65
|
+
- `ClickhouseSerializer` escapes backslashes, single quotes and all control characters
|
|
66
|
+
with backslash sequences (`O'Brien` → `'O\'Brien'`, newline → `\n`, other control characters → `\xHH`).
|
|
67
|
+
- `PostgresSerializer` doubles single quotes; strings containing backslashes or control
|
|
68
|
+
characters are emitted using the escape string syntax (`C:\dir` → `E'C:\\dir'`), which is
|
|
69
|
+
interpreted the same way regardless of the `standard_conforming_strings` server setting.
|
|
70
|
+
A NUL (`0x00`) character raises `ValueError`, since PostgreSQL cannot store it in text values.
|
|
71
|
+
|
|
72
|
+
The serialized literal is always a single printable line.
|
|
73
|
+
To customize escaping in your own serializer, override the `escape_string` method.
|
|
74
|
+
|
|
61
75
|
If you need to serialize a type not listed in the table, override the `serialize_other_object` method in your serializer:
|
|
62
76
|
|
|
63
77
|
```python
|
|
@@ -36,6 +36,20 @@ or create your own by inheriting from `BaseSerializer`.
|
|
|
36
36
|
| `date` | `'2025-01-01'` | `'2025-01-01'::date` | `toDate('2025-01-01')` |
|
|
37
37
|
| `list`/`tuple`/`set` | `(item1, item2, ...)` | `(item1, item2, ...)` | `(item1, item2, ...)` |
|
|
38
38
|
|
|
39
|
+
String values are escaped according to the dialect rules, so quotes, backslashes
|
|
40
|
+
and control characters cannot break the query:
|
|
41
|
+
|
|
42
|
+
- `BaseSerializer` doubles single quotes (`O'Brien` → `'O''Brien'`), as defined by the SQL standard.
|
|
43
|
+
- `ClickhouseSerializer` escapes backslashes, single quotes and all control characters
|
|
44
|
+
with backslash sequences (`O'Brien` → `'O\'Brien'`, newline → `\n`, other control characters → `\xHH`).
|
|
45
|
+
- `PostgresSerializer` doubles single quotes; strings containing backslashes or control
|
|
46
|
+
characters are emitted using the escape string syntax (`C:\dir` → `E'C:\\dir'`), which is
|
|
47
|
+
interpreted the same way regardless of the `standard_conforming_strings` server setting.
|
|
48
|
+
A NUL (`0x00`) character raises `ValueError`, since PostgreSQL cannot store it in text values.
|
|
49
|
+
|
|
50
|
+
The serialized literal is always a single printable line.
|
|
51
|
+
To customize escaping in your own serializer, override the `escape_string` method.
|
|
52
|
+
|
|
39
53
|
If you need to serialize a type not listed in the table, override the `serialize_other_object` method in your serializer:
|
|
40
54
|
|
|
41
55
|
```python
|
|
@@ -45,20 +45,25 @@ class BaseSerializer:
|
|
|
45
45
|
return f'{value}'
|
|
46
46
|
|
|
47
47
|
@staticmethod
|
|
48
|
-
def
|
|
49
|
-
|
|
48
|
+
def escape_string(value: str) -> str:
|
|
49
|
+
# SQL standard escaping: a single quote is escaped by doubling it
|
|
50
|
+
return value.replace("'", "''")
|
|
50
51
|
|
|
51
|
-
@
|
|
52
|
-
def
|
|
53
|
-
return f"'{value}'"
|
|
52
|
+
@classmethod
|
|
53
|
+
def serialize_string(cls, value: str) -> str:
|
|
54
|
+
return f"'{cls.escape_string(value)}'"
|
|
54
55
|
|
|
55
|
-
@
|
|
56
|
-
def
|
|
57
|
-
return f"'{
|
|
56
|
+
@classmethod
|
|
57
|
+
def serialize_uuid(cls, value: UUID) -> str:
|
|
58
|
+
return f"'{cls.escape_string(str(value))}'"
|
|
58
59
|
|
|
59
|
-
@
|
|
60
|
-
def
|
|
61
|
-
return f"'{value.isoformat()}'"
|
|
60
|
+
@classmethod
|
|
61
|
+
def serialize_datetime(cls, value: datetime) -> str:
|
|
62
|
+
return f"'{cls.escape_string(value.isoformat())}'"
|
|
63
|
+
|
|
64
|
+
@classmethod
|
|
65
|
+
def serialize_date(cls, value: date) -> str:
|
|
66
|
+
return f"'{cls.escape_string(value.isoformat())}'"
|
|
62
67
|
|
|
63
68
|
def serialize_collection(self, value: Collection) -> str:
|
|
64
69
|
items = ', '.join(self.serialize_value(item) for item in value)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from datetime import date, datetime
|
|
2
|
+
from typing import Dict
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from ddsql.serializers import BaseSerializer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _build_escape_translation() -> Dict[int, str]:
|
|
9
|
+
# https://clickhouse.com/docs/en/sql-reference/syntax#string
|
|
10
|
+
translation = {
|
|
11
|
+
ord('\\'): '\\\\',
|
|
12
|
+
ord("'"): "\\'",
|
|
13
|
+
ord('\0'): '\\0',
|
|
14
|
+
ord('\a'): '\\a',
|
|
15
|
+
ord('\b'): '\\b',
|
|
16
|
+
ord('\t'): '\\t',
|
|
17
|
+
ord('\n'): '\\n',
|
|
18
|
+
ord('\v'): '\\v',
|
|
19
|
+
ord('\f'): '\\f',
|
|
20
|
+
ord('\r'): '\\r',
|
|
21
|
+
}
|
|
22
|
+
# control characters without a named escape sequence
|
|
23
|
+
for code in range(0x20):
|
|
24
|
+
translation.setdefault(code, f'\\x{code:02X}')
|
|
25
|
+
translation[0x7F] = '\\x7F'
|
|
26
|
+
return translation
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
_ESCAPE_TRANSLATION = _build_escape_translation()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ClickhouseSerializer(BaseSerializer):
|
|
33
|
+
@staticmethod
|
|
34
|
+
def escape_string(value: str) -> str:
|
|
35
|
+
return value.translate(_ESCAPE_TRANSLATION)
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def serialize_uuid(cls, value: UUID) -> str:
|
|
39
|
+
return f"toUUID('{cls.escape_string(str(value))}')"
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def serialize_datetime(cls, value: datetime) -> str:
|
|
43
|
+
return f"parseDateTimeBestEffort('{cls.escape_string(value.isoformat())}')"
|
|
44
|
+
|
|
45
|
+
@classmethod
|
|
46
|
+
def serialize_date(cls, value: date) -> str:
|
|
47
|
+
return f"toDate('{cls.escape_string(value.isoformat())}')"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
__all__ = ('ClickhouseSerializer',)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import re
|
|
2
|
+
from datetime import date, datetime
|
|
3
|
+
from typing import Dict
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from ddsql.serializers import BaseSerializer
|
|
7
|
+
|
|
8
|
+
# Characters a regular literal cannot represent reliably: control characters have no escape
|
|
9
|
+
# sequences there, and a backslash changes meaning when standard_conforming_strings is off.
|
|
10
|
+
# Strings containing them are serialized with the escape string syntax (E'...') instead.
|
|
11
|
+
_ESCAPE_SYNTAX_REQUIRED = re.compile(r'[\\\x00-\x1F\x7F]')
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _build_escape_translation() -> Dict[int, str]:
|
|
15
|
+
# https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE
|
|
16
|
+
translation = {
|
|
17
|
+
ord('\\'): '\\\\',
|
|
18
|
+
ord("'"): "''",
|
|
19
|
+
ord('\b'): '\\b',
|
|
20
|
+
ord('\t'): '\\t',
|
|
21
|
+
ord('\n'): '\\n',
|
|
22
|
+
ord('\f'): '\\f',
|
|
23
|
+
ord('\r'): '\\r',
|
|
24
|
+
}
|
|
25
|
+
# control characters without a named escape sequence
|
|
26
|
+
for code in range(0x20):
|
|
27
|
+
translation.setdefault(code, f'\\x{code:02X}')
|
|
28
|
+
translation[0x7F] = '\\x7F'
|
|
29
|
+
return translation
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_ESCAPE_TRANSLATION = _build_escape_translation()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class PostgresSerializer(BaseSerializer):
|
|
36
|
+
@staticmethod
|
|
37
|
+
def escape_string(value: str) -> str:
|
|
38
|
+
# PostgreSQL follows the SQL standard: a single quote is escaped by doubling it
|
|
39
|
+
return value.replace("'", "''")
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def serialize_string(cls, value: str) -> str:
|
|
43
|
+
if '\x00' in value:
|
|
44
|
+
raise ValueError('PostgreSQL does not support the NUL (0x00) character in string values')
|
|
45
|
+
if _ESCAPE_SYNTAX_REQUIRED.search(value):
|
|
46
|
+
return f"E'{value.translate(_ESCAPE_TRANSLATION)}'"
|
|
47
|
+
return f"'{cls.escape_string(value)}'"
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def serialize_uuid(cls, value: UUID) -> str:
|
|
51
|
+
return f"'{cls.escape_string(str(value))}'::uuid"
|
|
52
|
+
|
|
53
|
+
@classmethod
|
|
54
|
+
def serialize_datetime(cls, value: datetime) -> str:
|
|
55
|
+
return f"'{cls.escape_string(value.isoformat())}'::timestamp"
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def serialize_date(cls, value: date) -> str:
|
|
59
|
+
return f"'{cls.escape_string(value.isoformat())}'::date"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
__all__ = ('PostgresSerializer',)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from datetime import date, datetime
|
|
2
|
-
from uuid import UUID
|
|
3
|
-
|
|
4
|
-
from ddsql.serializers import BaseSerializer
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class ClickhouseSerializer(BaseSerializer):
|
|
8
|
-
@staticmethod
|
|
9
|
-
def serialize_uuid(value: UUID) -> str:
|
|
10
|
-
return f"toUUID('{value}')"
|
|
11
|
-
|
|
12
|
-
@staticmethod
|
|
13
|
-
def serialize_datetime(value: datetime) -> str:
|
|
14
|
-
return f"parseDateTimeBestEffort('{value.isoformat()}')"
|
|
15
|
-
|
|
16
|
-
@staticmethod
|
|
17
|
-
def serialize_date(value: date) -> str:
|
|
18
|
-
return f"toDate('{value.isoformat()}')"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
__all__ = ('ClickhouseSerializer',)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from datetime import date, datetime
|
|
2
|
-
from uuid import UUID
|
|
3
|
-
|
|
4
|
-
from ddsql.serializers import BaseSerializer
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class PostgresSerializer(BaseSerializer):
|
|
8
|
-
@staticmethod
|
|
9
|
-
def serialize_uuid(value: UUID) -> str:
|
|
10
|
-
return f"'{value}'::uuid"
|
|
11
|
-
|
|
12
|
-
@staticmethod
|
|
13
|
-
def serialize_datetime(value: datetime) -> str:
|
|
14
|
-
return f"'{value.isoformat()}'::timestamp"
|
|
15
|
-
|
|
16
|
-
@staticmethod
|
|
17
|
-
def serialize_date(value: date) -> str:
|
|
18
|
-
return f"'{value.isoformat()}'::date"
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
__all__ = ('PostgresSerializer',)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|