sqloader 0.2.7__tar.gz → 0.2.9__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.
Files changed (30) hide show
  1. {sqloader-0.2.7/sqloader.egg-info → sqloader-0.2.9}/PKG-INFO +2 -1
  2. {sqloader-0.2.7 → sqloader-0.2.9}/pyproject.toml +2 -2
  3. {sqloader-0.2.7 → sqloader-0.2.9}/setup.py +2 -1
  4. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/migrator.py +7 -5
  5. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/mysql_async.py +3 -0
  6. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/postgresql.py +3 -0
  7. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/postgresql_async.py +3 -0
  8. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/sqlite3.py +3 -0
  9. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/sqlite3_async.py +3 -0
  10. {sqloader-0.2.7 → sqloader-0.2.9/sqloader.egg-info}/PKG-INFO +2 -1
  11. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader.egg-info/requires.txt +1 -0
  12. {sqloader-0.2.7 → sqloader-0.2.9}/tests/test_migrator.py +47 -1
  13. {sqloader-0.2.7 → sqloader-0.2.9}/LICENSE +0 -0
  14. {sqloader-0.2.7 → sqloader-0.2.9}/README.md +0 -0
  15. {sqloader-0.2.7 → sqloader-0.2.9}/setup.cfg +0 -0
  16. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/__init__.py +0 -0
  17. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/__main__.py +0 -0
  18. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/_async_prototype.py +0 -0
  19. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/_prototype.py +0 -0
  20. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/init.py +0 -0
  21. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/mysql.py +0 -0
  22. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader/sqloader.py +0 -0
  23. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader.egg-info/SOURCES.txt +0 -0
  24. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader.egg-info/dependency_links.txt +0 -0
  25. {sqloader-0.2.7 → sqloader-0.2.9}/sqloader.egg-info/top_level.txt +0 -0
  26. {sqloader-0.2.7 → sqloader-0.2.9}/tests/test_fetch_aliases.py +0 -0
  27. {sqloader-0.2.7 → sqloader-0.2.9}/tests/test_mysql.py +0 -0
  28. {sqloader-0.2.7 → sqloader-0.2.9}/tests/test_postgresql.py +0 -0
  29. {sqloader-0.2.7 → sqloader-0.2.9}/tests/test_sqlite.py +0 -0
  30. {sqloader-0.2.7 → sqloader-0.2.9}/tests/test_sqloader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqloader
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: A simple and extensible SQL migration and loader utility for Python.
5
5
  Home-page: https://github.com/horrible-gh/py_sqloader.git
6
6
  Author: horrible-gh
@@ -16,6 +16,7 @@ Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
17
  Requires-Dist: LogAssist
18
18
  Requires-Dist: pymysql>=1.1.1
19
+ Requires-Dist: sqlparse>=0.4.0
19
20
  Provides-Extra: postgresql
20
21
  Requires-Dist: psycopg2-binary>=2.9.0; extra == "postgresql"
21
22
  Provides-Extra: async-mysql
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "sqloader"
7
- version = "0.2.7"
7
+ version = "0.2.9"
8
8
  authors = [
9
9
  { name="horrible", email="shinjpn1@gmail.com" },
10
10
  ]
@@ -21,7 +21,7 @@ keywords = [
21
21
  "MySQL", "SQLite", "SQL migration", "schema management",
22
22
  "json sql loader"
23
23
  ]
24
- dependencies=['LogAssist', 'pymysql>=1.1.1']
24
+ dependencies=['LogAssist', 'pymysql>=1.1.1', 'sqlparse>=0.4.0']
25
25
 
26
26
  [project.optional-dependencies]
27
27
  postgresql = ["psycopg2-binary>=2.9.0"]
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='sqloader',
5
- version='0.2.7',
5
+ version='0.2.9',
6
6
  description='py_sqloader package',
7
7
  author='horrible-gh',
8
8
  author_email='shinjpn1@gmail.com',
@@ -23,6 +23,7 @@ setup(
23
23
  python_requires='>=3.6',
24
24
  install_requires=[
25
25
  "pymysql>=1.1.1", # MySQL sync is always included
26
+ "sqlparse>=0.4.0",
26
27
  ],
27
28
  extras_require={
28
29
  "postgresql": ["psycopg2-binary>=2.9.0"],
@@ -1,5 +1,7 @@
1
1
  import os
2
+ import re
2
3
  import glob
4
+ import sqlparse
3
5
  from ._prototype import DatabasePrototype, MYSQL, SQLITE, POSTGRESQL
4
6
 
5
7
  class DatabaseMigrator:
@@ -53,15 +55,15 @@ class DatabaseMigrator:
53
55
  full_path = os.path.join(self.migrations_path, migration)
54
56
 
55
57
  with open(full_path, 'r', encoding='utf-8') as f:
56
- sql_commands = f.read().split(';')
58
+ sql_commands = [s.strip() for s in sqlparse.split(f.read()) if s.strip()]
57
59
 
58
60
  try:
59
61
  # Execute all statements in a single transaction
60
62
  with self.db.begin_transaction() as txn:
61
63
  for command in sql_commands:
62
- command = command.strip()
63
- if command:
64
- txn.execute(command)
64
+ if not re.sub(r'--[^\n]*', '', command).strip():
65
+ continue
66
+ txn.execute(command)
65
67
  # Auto-commit on exit, auto-rollback on exception
66
68
 
67
69
  if self.db.db_type == SQLITE:
@@ -97,4 +99,4 @@ class DatabaseMigrator:
97
99
  Filenames match the relative paths inserted by apply_migration().
98
100
  """
99
101
  rows = self.db.fetch_all("SELECT filename FROM migrations")
100
- return {row['filename'] for row in rows}
102
+ return {row['filename'] for row in rows}
@@ -89,6 +89,9 @@ class AsyncMySqlWrapper(AsyncDatabasePrototype):
89
89
  print(f"Last query: {query}")
90
90
  raise
91
91
 
92
+ async def execute_query(self, query, params=None, commit=True):
93
+ return await self.execute(query, params, commit)
94
+
92
95
  async def fetchone(self, query, params=None):
93
96
  return await self.fetch_one(query, params)
94
97
 
@@ -68,6 +68,9 @@ class PostgreSQLWrapper(DatabasePrototype):
68
68
  self.pool.putconn(conn)
69
69
  query_semaphore.release()
70
70
 
71
+ def execute_query(self, query, params=None, commit=True):
72
+ return self.execute(query, params, commit)
73
+
71
74
  def fetchone(self, query, params=None):
72
75
  return self.fetch_one(query, params)
73
76
 
@@ -119,6 +119,9 @@ class AsyncPostgreSQLWrapper(AsyncDatabasePrototype):
119
119
  print(f"Last query: {q}")
120
120
  raise
121
121
 
122
+ async def execute_query(self, query, params=None, commit=True):
123
+ return await self.execute(query, params, commit)
124
+
122
125
  async def fetchone(self, query, params=None):
123
126
  return await self.fetch_one(query, params)
124
127
 
@@ -86,6 +86,9 @@ class SQLiteWrapper(DatabasePrototype):
86
86
  else:
87
87
  return self._execute_file(query, params, commit)
88
88
 
89
+ def execute_query(self, query, params=None, commit=True):
90
+ return self.execute(query, params, commit)
91
+
89
92
  def fetchone(self, query, params=None):
90
93
  return self.fetch_one(query, params)
91
94
 
@@ -94,6 +94,9 @@ class AsyncSQLiteWrapper(AsyncDatabasePrototype):
94
94
  await self._conn.rollback()
95
95
  raise
96
96
 
97
+ async def execute_query(self, query, params=None, commit=True):
98
+ return await self.execute(query, params, commit)
99
+
97
100
  async def fetchone(self, query, params=None):
98
101
  return await self.fetch_one(query, params)
99
102
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sqloader
3
- Version: 0.2.7
3
+ Version: 0.2.9
4
4
  Summary: A simple and extensible SQL migration and loader utility for Python.
5
5
  Home-page: https://github.com/horrible-gh/py_sqloader.git
6
6
  Author: horrible-gh
@@ -16,6 +16,7 @@ Description-Content-Type: text/markdown
16
16
  License-File: LICENSE
17
17
  Requires-Dist: LogAssist
18
18
  Requires-Dist: pymysql>=1.1.1
19
+ Requires-Dist: sqlparse>=0.4.0
19
20
  Provides-Extra: postgresql
20
21
  Requires-Dist: psycopg2-binary>=2.9.0; extra == "postgresql"
21
22
  Provides-Extra: async-mysql
@@ -1,5 +1,6 @@
1
1
  LogAssist
2
2
  pymysql>=1.1.1
3
+ sqlparse>=0.4.0
3
4
 
4
5
  [all]
5
6
  psycopg2-binary>=2.9.0
@@ -128,4 +128,50 @@ class TestAutoRun:
128
128
  def test_auto_run_false_does_not_apply(self, db, migration_dir):
129
129
  m = DatabaseMigrator(db, migration_dir, auto_run=False)
130
130
  applied = m.get_applied_migrations()
131
- assert len(applied) == 0
131
+ assert len(applied) == 0
132
+
133
+
134
+ # ---------------------------------------------------------------------------
135
+ # comment-only chunk (문제 1)
136
+ # ---------------------------------------------------------------------------
137
+
138
+ class TestCommentOnlyChunk:
139
+ def test_comment_only_file_does_not_raise(self, db):
140
+ """주석과 공백만 있는 청크는 실행 없이 건너뛰어야 한다."""
141
+ with tempfile.TemporaryDirectory() as tmpdir:
142
+ with open(os.path.join(tmpdir, "001_comments.sql"), "w") as f:
143
+ f.write(
144
+ "-- this is a comment\n"
145
+ "CREATE TABLE comment_test (id INTEGER PRIMARY KEY);\n"
146
+ "-- trailing comment\n"
147
+ )
148
+ m = DatabaseMigrator(db, tmpdir, auto_run=True)
149
+ applied = m.get_applied_migrations()
150
+ assert "001_comments.sql" in applied
151
+
152
+ rows = db.fetch_all(
153
+ "SELECT name FROM sqlite_master WHERE type='table' AND name='comment_test'"
154
+ )
155
+ assert len(list(rows)) == 1
156
+
157
+
158
+ # ---------------------------------------------------------------------------
159
+ # multi-statement SQL file (문제 2 — split 경계 검증)
160
+ # ---------------------------------------------------------------------------
161
+
162
+ class TestMultiStatementFile:
163
+ def test_multiple_statements_in_one_file(self, db):
164
+ """세미콜론으로 구분된 여러 구문이 모두 올바르게 실행되어야 한다."""
165
+ with tempfile.TemporaryDirectory() as tmpdir:
166
+ with open(os.path.join(tmpdir, "001_multi.sql"), "w") as f:
167
+ f.write(
168
+ "CREATE TABLE multi_a (id INTEGER PRIMARY KEY);\n"
169
+ "CREATE TABLE multi_b (id INTEGER PRIMARY KEY);\n"
170
+ )
171
+ DatabaseMigrator(db, tmpdir, auto_run=True)
172
+
173
+ for table in ("multi_a", "multi_b"):
174
+ rows = db.fetch_all(
175
+ f"SELECT name FROM sqlite_master WHERE type='table' AND name='{table}'"
176
+ )
177
+ assert len(list(rows)) == 1, f"Table {table} was not created"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes