sqlcompose 0.0.0__tar.gz → 0.0.2__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.
- sqlcompose-0.0.2/PKG-INFO +111 -0
- sqlcompose-0.0.0/PKG-INFO → sqlcompose-0.0.2/README.md +11 -20
- sqlcompose-0.0.2/pyproject.toml +50 -0
- sqlcompose-0.0.2/setup.cfg +4 -0
- sqlcompose-0.0.2/src/sqlcompose/__init__.py +10 -0
- sqlcompose-0.0.2/src/sqlcompose/__main__.py +13 -0
- sqlcompose-0.0.0/src/sqlcompose/__main__.py → sqlcompose-0.0.2/src/sqlcompose/core/app.py +7 -15
- sqlcompose-0.0.2/src/sqlcompose/core/circular_dependency_error.py +2 -0
- {sqlcompose-0.0.0 → sqlcompose-0.0.2}/src/sqlcompose/core/functions.py +41 -37
- {sqlcompose-0.0.0 → sqlcompose-0.0.2}/src/sqlcompose/core/include.py +1 -1
- sqlcompose-0.0.2/src/sqlcompose/py.typed +0 -0
- sqlcompose-0.0.2/src/sqlcompose.egg-info/PKG-INFO +111 -0
- sqlcompose-0.0.2/src/sqlcompose.egg-info/SOURCES.txt +20 -0
- sqlcompose-0.0.2/src/sqlcompose.egg-info/dependency_links.txt +1 -0
- sqlcompose-0.0.2/src/sqlcompose.egg-info/entry_points.txt +2 -0
- sqlcompose-0.0.2/src/sqlcompose.egg-info/requires.txt +4 -0
- sqlcompose-0.0.2/src/sqlcompose.egg-info/top_level.txt +1 -0
- sqlcompose-0.0.2/tests/test_app.py +29 -0
- sqlcompose-0.0.2/tests/test_compat.py +52 -0
- sqlcompose-0.0.2/tests/test_functions.py +40 -0
- sqlcompose-0.0.0/README.md +0 -68
- sqlcompose-0.0.0/pyproject.toml +0 -24
- sqlcompose-0.0.0/src/sqlcompose/__init__.py +0 -6
- {sqlcompose-0.0.0 → sqlcompose-0.0.2}/LICENSE +0 -0
- {sqlcompose-0.0.0 → sqlcompose-0.0.2}/src/sqlcompose/core/compat.py +0 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlcompose
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Composition of linked SQL files
|
|
5
|
+
Author-email: Anders Madsen <anders.madsen@alphavue.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: repository, https://github.com/apmadsen/sqlcompose
|
|
8
|
+
Keywords: sql,composition,windows,linux
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Development Status :: 6 - Mature
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest>=8.3.0; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-cov>=6.1.0; extra == "test"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
[](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml)
|
|
35
|
+
[](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test-coverage.yml)
|
|
36
|
+

|
|
37
|
+

|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# sqlcompose: Composition of linked SQL files
|
|
42
|
+
sqlcompose allows you to compose sql files from multiple files by introducing `INCLUDE` keywords. The SQL output is composed as CTE's or Common Table Expressions.
|
|
43
|
+
|
|
44
|
+
## Examples
|
|
45
|
+
__Execute the script directly:__
|
|
46
|
+
```console
|
|
47
|
+
sqlcompose query.sql
|
|
48
|
+
```
|
|
49
|
+
```bash
|
|
50
|
+
sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
|
|
51
|
+
sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
__Import it in another script:__
|
|
55
|
+
```python
|
|
56
|
+
from sqlcompose import load, loads
|
|
57
|
+
# method 1 : loading from a file
|
|
58
|
+
sql1 = load("query.sql")
|
|
59
|
+
|
|
60
|
+
# method 2 : loading from an SQL string
|
|
61
|
+
sql2 = loads("""
|
|
62
|
+
select *
|
|
63
|
+
from dataset.table main
|
|
64
|
+
inner join $INCLUDE(other.sql) other
|
|
65
|
+
on other.field = main.field
|
|
66
|
+
""")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Preparing SQL scripts
|
|
70
|
+
Insert a `$INCLUDE(filename)` where the reference to the file should be in the resulting SQL, keeping in mind that references are loaded relative to the file loaded or the current working dir in case of an SQL string.
|
|
71
|
+
|
|
72
|
+
```sql
|
|
73
|
+
--main-query.sql
|
|
74
|
+
select * from $INCLUDE(includes\included-query2.sql)
|
|
75
|
+
```
|
|
76
|
+
```sql
|
|
77
|
+
--included-query1.sql
|
|
78
|
+
select 1 as test
|
|
79
|
+
```
|
|
80
|
+
```sql
|
|
81
|
+
--included-query2.sql
|
|
82
|
+
select * from $INCLUDE(included-query1.sql)
|
|
83
|
+
union all
|
|
84
|
+
select * from $INCLUDE(nested\included-query3.sql)
|
|
85
|
+
```
|
|
86
|
+
```sql
|
|
87
|
+
--nested\included-query3.sql
|
|
88
|
+
select 1 as test
|
|
89
|
+
```
|
|
90
|
+
Which outputs:
|
|
91
|
+
```sql
|
|
92
|
+
WITH Q_1_1 AS (
|
|
93
|
+
WITH Q_2_1 AS (
|
|
94
|
+
--includes\included-query1.sql
|
|
95
|
+
select 1 as test
|
|
96
|
+
), Q_2_2 AS (
|
|
97
|
+
--includes\nested\included-query3.sql
|
|
98
|
+
select 1 as test
|
|
99
|
+
), Q_2 AS (
|
|
100
|
+
--includes\included-query2.sql
|
|
101
|
+
select * from Q_2_1
|
|
102
|
+
union all
|
|
103
|
+
select * from Q_2_2
|
|
104
|
+
)
|
|
105
|
+
SELECT * FROM Q_2
|
|
106
|
+
), Q_1 AS (
|
|
107
|
+
--test\main-query.sql
|
|
108
|
+
select * from Q_1_1
|
|
109
|
+
)
|
|
110
|
+
SELECT * FROM Q_1
|
|
111
|
+
```
|
|
@@ -1,29 +1,21 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
Version
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Author-email: anders.madsen@alphavue.com
|
|
8
|
-
Requires-Python: >=3.10,<=3.13
|
|
9
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
-
Classifier: Operating System :: OS Independent
|
|
11
|
-
Classifier: Programming Language :: Python :: 3
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Project-URL: Repository, https://github.com/apmadsen/sqlcompose
|
|
17
|
-
Description-Content-Type: text/markdown
|
|
1
|
+
[](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml)
|
|
2
|
+
[](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test-coverage.yml)
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
18
7
|
|
|
19
8
|
# sqlcompose: Composition of linked SQL files
|
|
20
9
|
sqlcompose allows you to compose sql files from multiple files by introducing `INCLUDE` keywords. The SQL output is composed as CTE's or Common Table Expressions.
|
|
21
10
|
|
|
22
11
|
## Examples
|
|
23
12
|
__Execute the script directly:__
|
|
24
|
-
```
|
|
13
|
+
```console
|
|
25
14
|
sqlcompose query.sql
|
|
26
|
-
|
|
15
|
+
```
|
|
16
|
+
```bash
|
|
17
|
+
sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
|
|
18
|
+
sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
|
|
27
19
|
```
|
|
28
20
|
|
|
29
21
|
__Import it in another script:__
|
|
@@ -84,4 +76,3 @@ WITH Q_1_1 AS (
|
|
|
84
76
|
)
|
|
85
77
|
SELECT * FROM Q_1
|
|
86
78
|
```
|
|
87
|
-
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "sqlcompose"
|
|
3
|
+
dynamic = ["version"]
|
|
4
|
+
description = "Composition of linked SQL files"
|
|
5
|
+
keywords = ["sql", "composition", "windows", "linux"]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
authors = [
|
|
8
|
+
{ name = "Anders Madsen", email = "anders.madsen@alphavue.com" }
|
|
9
|
+
]
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 5 - Production/Stable",
|
|
13
|
+
"Development Status :: 6 - Mature",
|
|
14
|
+
"Operating System :: Microsoft :: Windows",
|
|
15
|
+
"Operating System :: POSIX :: Linux",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Natural Language :: English",
|
|
18
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.9",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Programming Language :: Python",
|
|
26
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
27
|
+
"Topic :: Software Development :: Libraries",
|
|
28
|
+
"Typing :: Typed"
|
|
29
|
+
]
|
|
30
|
+
dependencies = []
|
|
31
|
+
requires-python = ">=3.10"
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
repository = "https://github.com/apmadsen/sqlcompose"
|
|
35
|
+
|
|
36
|
+
[project.scripts]
|
|
37
|
+
"sqlcompose" = "sqlcompose.main:main"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
test = [
|
|
41
|
+
"pytest>=8.3.0",
|
|
42
|
+
"pytest-cov>=6.1.0",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[tool.setuptools.dynamic]
|
|
46
|
+
version = { attr = 'sqlcompose.__init__.__version__' }
|
|
47
|
+
|
|
48
|
+
[build-system]
|
|
49
|
+
requires = ["setuptools >= 77.0.3"]
|
|
50
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
from os import path
|
|
2
|
-
from sys import exit
|
|
3
2
|
from argparse import ArgumentParser
|
|
4
3
|
|
|
5
4
|
from sqlcompose.core.functions import load, loads
|
|
6
5
|
|
|
7
6
|
|
|
8
|
-
def
|
|
7
|
+
def app(args: list[str]) -> tuple[str, int ]:
|
|
9
8
|
try:
|
|
10
9
|
parser = ArgumentParser(prog = "sqlcompose")
|
|
11
10
|
parser.add_argument("input", type=str, help = "SQL expression or location of an SQL file")
|
|
12
|
-
pargs = parser.parse_args()
|
|
11
|
+
pargs = parser.parse_args(args)
|
|
13
12
|
|
|
14
13
|
if path.isfile(pargs.input):
|
|
15
14
|
sql = load(pargs.input)
|
|
16
15
|
else:
|
|
17
16
|
sql = loads(pargs.input)
|
|
18
17
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return
|
|
22
|
-
except
|
|
23
|
-
|
|
24
|
-
except Exception as ex:
|
|
25
|
-
print(f"Unexpected error: {ex}")
|
|
26
|
-
exit(1)
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
if __name__ == "__main__":
|
|
30
|
-
main()
|
|
18
|
+
return sql, 0
|
|
19
|
+
except SystemExit as ex:
|
|
20
|
+
return str(ex), 2
|
|
21
|
+
except Exception as ex: # pragma: no cover
|
|
22
|
+
return f"Unexpected error: {ex}", 1
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
from os import path
|
|
2
|
-
from typing import Sequence
|
|
2
|
+
from typing import Sequence
|
|
3
3
|
from os import path
|
|
4
4
|
from re import compile, sub, escape, IGNORECASE
|
|
5
5
|
from textwrap import indent
|
|
6
6
|
|
|
7
|
+
from sqlcompose.core.circular_dependency_error import CircularDependencyError
|
|
7
8
|
from sqlcompose.core.include import Include
|
|
8
9
|
from sqlcompose.core.compat import fix_path, get_relative_path
|
|
9
10
|
|
|
@@ -19,7 +20,7 @@ def loads(sql: str) -> str:
|
|
|
19
20
|
Returns:
|
|
20
21
|
str: The composed query
|
|
21
22
|
"""
|
|
22
|
-
return
|
|
23
|
+
return compose(sql, "SQL", path.curdir, path.curdir)
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
def load(filename: str) -> str:
|
|
@@ -40,7 +41,7 @@ def load(filename: str) -> str:
|
|
|
40
41
|
|
|
41
42
|
|
|
42
43
|
with open(filename, "r", encoding="utf-8") as file:
|
|
43
|
-
return
|
|
44
|
+
return compose(file.read(), filename, filename, path.dirname(filename))
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
def compose(
|
|
@@ -49,52 +50,55 @@ def compose(
|
|
|
49
50
|
file_path: str,
|
|
50
51
|
root: str,
|
|
51
52
|
level: int = 1,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
) -> str | None:
|
|
53
|
+
stack: list[str] | None = None
|
|
54
|
+
) -> str:
|
|
55
55
|
|
|
56
56
|
file_path = fix_path(file_path)
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
stack = stack or []
|
|
58
|
+
parent = stack[-1] if stack else None
|
|
59
|
+
name = get_relative_path(name, root)
|
|
60
|
+
index = 1
|
|
61
|
+
included: list[str] = []
|
|
59
62
|
includes: list[Include] = []
|
|
60
|
-
|
|
63
|
+
stack.append(name)
|
|
61
64
|
|
|
62
|
-
if
|
|
63
|
-
|
|
64
|
-
return None
|
|
65
|
-
elif name in included:
|
|
66
|
-
raise Exception(f"Circular dependency detected: File \"{get_relative_path(file_path, root)}\" has already been already included")
|
|
67
|
-
else:
|
|
68
|
-
included.append(name)
|
|
69
|
-
included1[name] = (file_path, level)
|
|
70
|
-
|
|
71
|
-
index = 1
|
|
65
|
+
if len(stack) > 1 and name in (stack[1:-1]):
|
|
66
|
+
raise CircularDependencyError
|
|
72
67
|
|
|
73
68
|
for match in REGEX_INCLUDE.finditer(sql):
|
|
74
69
|
file_path_inner = fix_path(path.join(path.dirname(file_path), match.group(1)))
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
70
|
+
if file_path_inner not in included:
|
|
71
|
+
included.append(file_path_inner)
|
|
72
|
+
try:
|
|
73
|
+
with open(file_path_inner, "r", encoding="utf-8") as file:
|
|
74
|
+
composed = compose(
|
|
75
|
+
file.read(),
|
|
76
|
+
match.group(1),
|
|
77
|
+
file_path_inner,
|
|
78
|
+
root,
|
|
79
|
+
level + 1,
|
|
80
|
+
stack
|
|
81
|
+
)
|
|
82
|
+
includes.append(
|
|
83
|
+
Include(
|
|
84
|
+
composed,
|
|
85
|
+
f"Q_{level}_{index}",
|
|
86
|
+
match.group(0),
|
|
87
|
+
match.group(1)
|
|
88
|
+
)
|
|
91
89
|
)
|
|
92
|
-
|
|
93
|
-
|
|
90
|
+
index = index + 1
|
|
91
|
+
except FileNotFoundError:
|
|
92
|
+
if parent is not None:
|
|
93
|
+
raise FileNotFoundError(f"Include failed: File \"{get_relative_path(file_path_inner, root)}\" which was referred to in \"{get_relative_path(parent, root)}\", was not found...")
|
|
94
|
+
else:
|
|
95
|
+
raise FileNotFoundError(f"Include failed: File \"{get_relative_path(file_path_inner, root)}\" was not found...")
|
|
94
96
|
|
|
95
97
|
for include in includes:
|
|
96
98
|
sql = sub(escape(include.match), include.name, sql)
|
|
97
99
|
|
|
100
|
+
stack.pop()
|
|
101
|
+
|
|
98
102
|
return wrap_cte_sql(includes, sql, level, name)
|
|
99
103
|
|
|
100
104
|
def wrap_cte_sql(includes: Sequence[Include], sql: str, level: int, source: str) -> str:
|
|
File without changes
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: sqlcompose
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Composition of linked SQL files
|
|
5
|
+
Author-email: Anders Madsen <anders.madsen@alphavue.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: repository, https://github.com/apmadsen/sqlcompose
|
|
8
|
+
Keywords: sql,composition,windows,linux
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
|
+
Classifier: Development Status :: 6 - Mature
|
|
11
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Natural Language :: English
|
|
15
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
25
|
+
Classifier: Typing :: Typed
|
|
26
|
+
Requires-Python: >=3.10
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest>=8.3.0; extra == "test"
|
|
31
|
+
Requires-Dist: pytest-cov>=6.1.0; extra == "test"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
[](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test.yml)
|
|
35
|
+
[](https://github.com/apmadsen/sqlcompose/actions/workflows/python-test-coverage.yml)
|
|
36
|
+

|
|
37
|
+

|
|
38
|
+

|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# sqlcompose: Composition of linked SQL files
|
|
42
|
+
sqlcompose allows you to compose sql files from multiple files by introducing `INCLUDE` keywords. The SQL output is composed as CTE's or Common Table Expressions.
|
|
43
|
+
|
|
44
|
+
## Examples
|
|
45
|
+
__Execute the script directly:__
|
|
46
|
+
```console
|
|
47
|
+
sqlcompose query.sql
|
|
48
|
+
```
|
|
49
|
+
```bash
|
|
50
|
+
sqlcompose 'select * from $INCLUDE(included-query1.sql)' # on linux
|
|
51
|
+
sqlcompose "select * from $INCLUDE(included-query1.sql)" # on windows
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
__Import it in another script:__
|
|
55
|
+
```python
|
|
56
|
+
from sqlcompose import load, loads
|
|
57
|
+
# method 1 : loading from a file
|
|
58
|
+
sql1 = load("query.sql")
|
|
59
|
+
|
|
60
|
+
# method 2 : loading from an SQL string
|
|
61
|
+
sql2 = loads("""
|
|
62
|
+
select *
|
|
63
|
+
from dataset.table main
|
|
64
|
+
inner join $INCLUDE(other.sql) other
|
|
65
|
+
on other.field = main.field
|
|
66
|
+
""")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Preparing SQL scripts
|
|
70
|
+
Insert a `$INCLUDE(filename)` where the reference to the file should be in the resulting SQL, keeping in mind that references are loaded relative to the file loaded or the current working dir in case of an SQL string.
|
|
71
|
+
|
|
72
|
+
```sql
|
|
73
|
+
--main-query.sql
|
|
74
|
+
select * from $INCLUDE(includes\included-query2.sql)
|
|
75
|
+
```
|
|
76
|
+
```sql
|
|
77
|
+
--included-query1.sql
|
|
78
|
+
select 1 as test
|
|
79
|
+
```
|
|
80
|
+
```sql
|
|
81
|
+
--included-query2.sql
|
|
82
|
+
select * from $INCLUDE(included-query1.sql)
|
|
83
|
+
union all
|
|
84
|
+
select * from $INCLUDE(nested\included-query3.sql)
|
|
85
|
+
```
|
|
86
|
+
```sql
|
|
87
|
+
--nested\included-query3.sql
|
|
88
|
+
select 1 as test
|
|
89
|
+
```
|
|
90
|
+
Which outputs:
|
|
91
|
+
```sql
|
|
92
|
+
WITH Q_1_1 AS (
|
|
93
|
+
WITH Q_2_1 AS (
|
|
94
|
+
--includes\included-query1.sql
|
|
95
|
+
select 1 as test
|
|
96
|
+
), Q_2_2 AS (
|
|
97
|
+
--includes\nested\included-query3.sql
|
|
98
|
+
select 1 as test
|
|
99
|
+
), Q_2 AS (
|
|
100
|
+
--includes\included-query2.sql
|
|
101
|
+
select * from Q_2_1
|
|
102
|
+
union all
|
|
103
|
+
select * from Q_2_2
|
|
104
|
+
)
|
|
105
|
+
SELECT * FROM Q_2
|
|
106
|
+
), Q_1 AS (
|
|
107
|
+
--test\main-query.sql
|
|
108
|
+
select * from Q_1_1
|
|
109
|
+
)
|
|
110
|
+
SELECT * FROM Q_1
|
|
111
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/sqlcompose/__init__.py
|
|
5
|
+
src/sqlcompose/__main__.py
|
|
6
|
+
src/sqlcompose/py.typed
|
|
7
|
+
src/sqlcompose.egg-info/PKG-INFO
|
|
8
|
+
src/sqlcompose.egg-info/SOURCES.txt
|
|
9
|
+
src/sqlcompose.egg-info/dependency_links.txt
|
|
10
|
+
src/sqlcompose.egg-info/entry_points.txt
|
|
11
|
+
src/sqlcompose.egg-info/requires.txt
|
|
12
|
+
src/sqlcompose.egg-info/top_level.txt
|
|
13
|
+
src/sqlcompose/core/app.py
|
|
14
|
+
src/sqlcompose/core/circular_dependency_error.py
|
|
15
|
+
src/sqlcompose/core/compat.py
|
|
16
|
+
src/sqlcompose/core/functions.py
|
|
17
|
+
src/sqlcompose/core/include.py
|
|
18
|
+
tests/test_app.py
|
|
19
|
+
tests/test_compat.py
|
|
20
|
+
tests/test_functions.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
sqlcompose
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# pyright: basic
|
|
2
|
+
from unittest import TestCase, main
|
|
3
|
+
|
|
4
|
+
from sqlcompose.core.app import app
|
|
5
|
+
|
|
6
|
+
class Test(TestCase):
|
|
7
|
+
# def test_nonexisting(self):
|
|
8
|
+
# _result, code = app(["nonexisting.sql"])
|
|
9
|
+
# self.assertEqual(code, 1)
|
|
10
|
+
|
|
11
|
+
def test_missing_args(self):
|
|
12
|
+
_result, code = app([])
|
|
13
|
+
self.assertEqual(code, 2)
|
|
14
|
+
|
|
15
|
+
def test_existing_file_by_path(self):
|
|
16
|
+
result, code = app(["tests/main-query.sql"])
|
|
17
|
+
self.assertGreater(len(result), 0)
|
|
18
|
+
self.assertEqual(code, 0)
|
|
19
|
+
|
|
20
|
+
def test_sql(self):
|
|
21
|
+
result, code = app(["SELECT * FROM $INCLUDE(tests/main-query.sql)"])
|
|
22
|
+
# print(result)
|
|
23
|
+
self.assertGreater(len(result), 0)
|
|
24
|
+
self.assertEqual(code, 0)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
if __name__ == '__main__':
|
|
28
|
+
main()
|
|
29
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# pyright: basic
|
|
2
|
+
from unittest import TestCase, main
|
|
3
|
+
from sys import platform
|
|
4
|
+
|
|
5
|
+
from sqlcompose.core.compat import fix_path, get_relative_path
|
|
6
|
+
|
|
7
|
+
class Test(TestCase):
|
|
8
|
+
|
|
9
|
+
def test_fix_path(self):
|
|
10
|
+
if platform == "win32":
|
|
11
|
+
tests = {
|
|
12
|
+
"sql\\file.sql" : "sql\\file.sql",
|
|
13
|
+
"sql\\file.sql" : "sql/file.sql",
|
|
14
|
+
}
|
|
15
|
+
else:
|
|
16
|
+
tests = {
|
|
17
|
+
"sql/file.sql" : "sql\\file.sql",
|
|
18
|
+
"sql/file.sql" : "sql/file.sql",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
for expected_result, file_path in tests.items():
|
|
22
|
+
result = fix_path(file_path)
|
|
23
|
+
self.assertEqual(result, expected_result)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def test_get_relative_path(self):
|
|
27
|
+
if platform == "win32":
|
|
28
|
+
tests = {
|
|
29
|
+
"sql\\file.sql" : ("sql\\file.sql", "sql\\file.sql"),
|
|
30
|
+
"sql\\file.sql" : ("sql\\file.sql", "c:\\app\\"),
|
|
31
|
+
"sql\\file.sql" : ("c:\\app\\sql\\file.sql", "c:\\app\\"),
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
p = "sql\\file.sql"
|
|
35
|
+
self.assertEqual(p, get_relative_path(p, p))
|
|
36
|
+
else:
|
|
37
|
+
tests = {
|
|
38
|
+
"sql/file.sql" : ("sql/file.sql", "sql/file.sql"),
|
|
39
|
+
"sql/file.sql" : ("sql/file.sql", "/app/"),
|
|
40
|
+
"sql/file.sql" : ("/app/sql/file.sql", "/app/"),
|
|
41
|
+
}
|
|
42
|
+
p = "sql/file.sql"
|
|
43
|
+
self.assertEqual(p, get_relative_path(p, p))
|
|
44
|
+
|
|
45
|
+
for expected_result, (file_path, root) in tests.items():
|
|
46
|
+
result = get_relative_path(file_path, root)
|
|
47
|
+
self.assertEqual(result, expected_result)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
if __name__ == '__main__':
|
|
51
|
+
main()
|
|
52
|
+
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# pyright: basic
|
|
2
|
+
from unittest import TestCase, main
|
|
3
|
+
|
|
4
|
+
from sqlcompose import loads, load, CircularDependencyError
|
|
5
|
+
from sqlcompose.core.functions import compose
|
|
6
|
+
|
|
7
|
+
class Test(TestCase):
|
|
8
|
+
def test_nonexisting(self):
|
|
9
|
+
self.assertRaises(FileNotFoundError, load, "nonexisting.sql")
|
|
10
|
+
self.assertRaises(FileNotFoundError, load, "tests/non_existing_include.sql")
|
|
11
|
+
self.assertRaises(FileNotFoundError, compose, "select * from $INCLUDE(some_file_that_does_not_exist.sql)", "SQL", ".", ".")
|
|
12
|
+
|
|
13
|
+
filename = "tests/existing_include.sql"
|
|
14
|
+
with open(filename, "r", encoding="utf-8") as file:
|
|
15
|
+
self.assertRaises(FileNotFoundError, compose, file.read(), filename, filename, ".")
|
|
16
|
+
|
|
17
|
+
def test_reuse_composition(self):
|
|
18
|
+
result = compose("select * from $INCLUDE(tests/includes/included-query3.sql)", "SQL", ".", ".")
|
|
19
|
+
# print(result)
|
|
20
|
+
self.assertTrue(len(result) > 0)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_existing_file_by_path(self):
|
|
24
|
+
result = load("tests/main-query.sql")
|
|
25
|
+
# print(result)
|
|
26
|
+
self.assertTrue(len(result) > 0)
|
|
27
|
+
|
|
28
|
+
def test_sql(self):
|
|
29
|
+
result = loads("SELECT * FROM $INCLUDE(tests/main-query.sql)")
|
|
30
|
+
# print(result)
|
|
31
|
+
self.assertTrue(len(result) > 0)
|
|
32
|
+
|
|
33
|
+
def test_circular_dependency(self):
|
|
34
|
+
self.assertRaises(CircularDependencyError, load, "tests/circular_left.sql")
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
if __name__ == '__main__':
|
|
39
|
+
main()
|
|
40
|
+
|
sqlcompose-0.0.0/README.md
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# sqlcompose: Composition of linked SQL files
|
|
2
|
-
sqlcompose allows you to compose sql files from multiple files by introducing `INCLUDE` keywords. The SQL output is composed as CTE's or Common Table Expressions.
|
|
3
|
-
|
|
4
|
-
## Examples
|
|
5
|
-
__Execute the script directly:__
|
|
6
|
-
```
|
|
7
|
-
sqlcompose query.sql
|
|
8
|
-
sqlcompose "select * from $INCLUDE(included-query1.sql)"
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
__Import it in another script:__
|
|
12
|
-
```python
|
|
13
|
-
from sqlcompose import load, loads
|
|
14
|
-
# method 1 : loading from a file
|
|
15
|
-
sql1 = load("query.sql")
|
|
16
|
-
|
|
17
|
-
# method 2 : loading from an SQL string
|
|
18
|
-
sql2 = loads("""
|
|
19
|
-
select *
|
|
20
|
-
from dataset.table main
|
|
21
|
-
inner join $INCLUDE(other.sql) other
|
|
22
|
-
on other.field = main.field
|
|
23
|
-
""")
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
## Preparing SQL scripts
|
|
27
|
-
Insert a `$INCLUDE(filename)` where the reference to the file should be in the resulting SQL, keeping in mind that references are loaded relative to the file loaded or the current working dir in case of an SQL string.
|
|
28
|
-
|
|
29
|
-
```sql
|
|
30
|
-
--main-query.sql
|
|
31
|
-
select * from $INCLUDE(includes\included-query2.sql)
|
|
32
|
-
```
|
|
33
|
-
```sql
|
|
34
|
-
--included-query1.sql
|
|
35
|
-
select 1 as test
|
|
36
|
-
```
|
|
37
|
-
```sql
|
|
38
|
-
--included-query2.sql
|
|
39
|
-
select * from $INCLUDE(included-query1.sql)
|
|
40
|
-
union all
|
|
41
|
-
select * from $INCLUDE(nested\included-query3.sql)
|
|
42
|
-
```
|
|
43
|
-
```sql
|
|
44
|
-
--nested\included-query3.sql
|
|
45
|
-
select 1 as test
|
|
46
|
-
```
|
|
47
|
-
Which outputs:
|
|
48
|
-
```sql
|
|
49
|
-
WITH Q_1_1 AS (
|
|
50
|
-
WITH Q_2_1 AS (
|
|
51
|
-
--includes\included-query1.sql
|
|
52
|
-
select 1 as test
|
|
53
|
-
), Q_2_2 AS (
|
|
54
|
-
--includes\nested\included-query3.sql
|
|
55
|
-
select 1 as test
|
|
56
|
-
), Q_2 AS (
|
|
57
|
-
--includes\included-query2.sql
|
|
58
|
-
select * from Q_2_1
|
|
59
|
-
union all
|
|
60
|
-
select * from Q_2_2
|
|
61
|
-
)
|
|
62
|
-
SELECT * FROM Q_2
|
|
63
|
-
), Q_1 AS (
|
|
64
|
-
--test\main-query.sql
|
|
65
|
-
select * from Q_1_1
|
|
66
|
-
)
|
|
67
|
-
SELECT * FROM Q_1
|
|
68
|
-
```
|
sqlcompose-0.0.0/pyproject.toml
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
[tool.poetry]
|
|
2
|
-
name = "sqlcompose"
|
|
3
|
-
version = "0.0.0"
|
|
4
|
-
description = ""
|
|
5
|
-
readme = "README.md"
|
|
6
|
-
authors = ["Anders Madsen <anders.madsen@alphavue.com>"]
|
|
7
|
-
license = "MIT"
|
|
8
|
-
repository = "https://github.com/apmadsen/sqlcompose"
|
|
9
|
-
classifiers = [
|
|
10
|
-
"Operating System :: OS Independent"
|
|
11
|
-
]
|
|
12
|
-
packages = [
|
|
13
|
-
{ include = "sqlcompose", from = "src" }
|
|
14
|
-
]
|
|
15
|
-
|
|
16
|
-
[tool.poetry.scripts]
|
|
17
|
-
"sqlcompose" = "sqlcompose.main:main"
|
|
18
|
-
|
|
19
|
-
[tool.poetry.dependencies]
|
|
20
|
-
python = ">=3.10,<=3.13"
|
|
21
|
-
|
|
22
|
-
[build-system]
|
|
23
|
-
requires = ["poetry-core>=1.0.0"]
|
|
24
|
-
build-backend = "poetry.core.masonry.api"
|
|
File without changes
|
|
File without changes
|