execsql2 2.1.2__py3-none-any.whl → 2.2.1__py3-none-any.whl
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.
- execsql/cli/__init__.py +436 -0
- execsql/cli/dsn.py +86 -0
- execsql/cli/help.py +140 -0
- execsql/{cli.py → cli/run.py} +14 -589
- execsql/config.py +13 -1
- execsql/db/access.py +16 -12
- execsql/db/base.py +158 -90
- execsql/db/dsn.py +6 -5
- execsql/db/duckdb.py +2 -2
- execsql/db/firebird.py +23 -19
- execsql/db/mysql.py +8 -7
- execsql/db/oracle.py +11 -11
- execsql/db/postgres.py +28 -16
- execsql/db/sqlite.py +12 -11
- execsql/db/sqlserver.py +5 -3
- execsql/exceptions.py +7 -7
- execsql/exporters/base.py +6 -1
- execsql/exporters/delimited.py +44 -35
- execsql/exporters/duckdb.py +2 -2
- execsql/exporters/feather.py +6 -6
- execsql/exporters/html.py +83 -69
- execsql/exporters/json.py +50 -42
- execsql/exporters/latex.py +33 -27
- execsql/exporters/ods.py +4 -4
- execsql/exporters/parquet.py +2 -2
- execsql/exporters/pretty.py +11 -9
- execsql/exporters/raw.py +17 -13
- execsql/exporters/sqlite.py +2 -2
- execsql/exporters/templates.py +23 -15
- execsql/exporters/values.py +22 -20
- execsql/exporters/xls.py +4 -4
- execsql/exporters/xml.py +28 -13
- execsql/importers/base.py +4 -4
- execsql/importers/csv.py +6 -6
- execsql/importers/feather.py +4 -4
- execsql/importers/ods.py +4 -4
- execsql/importers/xls.py +4 -4
- execsql/metacommands/__init__.py +518 -67
- execsql/metacommands/conditions.py +101 -27
- execsql/metacommands/control.py +8 -4
- execsql/metacommands/data.py +6 -6
- execsql/metacommands/debug.py +6 -2
- execsql/metacommands/io.py +67 -1310
- execsql/metacommands/io_export.py +442 -0
- execsql/metacommands/io_fileops.py +287 -0
- execsql/metacommands/io_import.py +398 -0
- execsql/metacommands/io_write.py +248 -0
- execsql/metacommands/prompt.py +22 -66
- execsql/metacommands/system.py +7 -2
- execsql/py.typed +0 -0
- execsql/script.py +49 -5
- execsql/types.py +20 -20
- execsql/utils/fileio.py +15 -8
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/METADATA +6 -6
- execsql2-2.2.1.dist-info/RECORD +104 -0
- execsql2-2.1.2.dist-info/RECORD +0 -96
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/READ_ME.rst +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/config_settings.sqlite +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/execsql.conf +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/make_config_db.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/md_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/md_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/md_upsert.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/pg_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/pg_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/pg_upsert.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/script_template.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/ss_compare.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/ss_glossary.sql +0 -0
- {execsql2-2.1.2.data → execsql2-2.2.1.data}/data/execsql2_extras/ss_upsert.sql +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/WHEEL +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/entry_points.txt +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/licenses/LICENSE.txt +0 -0
- {execsql2-2.1.2.dist-info → execsql2-2.2.1.dist-info}/licenses/NOTICE +0 -0
execsql/db/oracle.py
CHANGED
|
@@ -24,7 +24,7 @@ class OracleDatabase(Database):
|
|
|
24
24
|
db_name: str,
|
|
25
25
|
user_name: str | None,
|
|
26
26
|
need_passwd: bool = False,
|
|
27
|
-
port: int | None =
|
|
27
|
+
port: int | None = 1521,
|
|
28
28
|
encoding: str | None = "UTF8",
|
|
29
29
|
password: str | None = None,
|
|
30
30
|
) -> None:
|
|
@@ -95,9 +95,9 @@ class OracleDatabase(Database):
|
|
|
95
95
|
raise
|
|
96
96
|
except ErrInfo:
|
|
97
97
|
raise
|
|
98
|
-
except Exception:
|
|
98
|
+
except Exception as e:
|
|
99
99
|
msg = f"Failed to open Oracle database {self.db_name} on {self.server_name}"
|
|
100
|
-
raise ErrInfo(type="exception", exception_msg=exception_desc(), other_msg=msg)
|
|
100
|
+
raise ErrInfo(type="exception", exception_msg=exception_desc(), other_msg=msg) from e
|
|
101
101
|
|
|
102
102
|
def execute(self, sql: Any, paramlist: list | None = None) -> None:
|
|
103
103
|
# Strip any semicolon off the end and pass to the parent method.
|
|
@@ -141,14 +141,14 @@ class OracleDatabase(Database):
|
|
|
141
141
|
curs.execute(sql, params)
|
|
142
142
|
except ErrInfo:
|
|
143
143
|
raise
|
|
144
|
-
except Exception:
|
|
144
|
+
except Exception as e:
|
|
145
145
|
self.rollback()
|
|
146
146
|
raise ErrInfo(
|
|
147
147
|
type="db",
|
|
148
148
|
command_text=sql,
|
|
149
149
|
exception_msg=exception_desc(),
|
|
150
150
|
other_msg=f"Failed test for existence of table {table_name} in {self.name()}",
|
|
151
|
-
)
|
|
151
|
+
) from e
|
|
152
152
|
rows = curs.fetchall()
|
|
153
153
|
curs.close()
|
|
154
154
|
return len(rows) > 0
|
|
@@ -170,14 +170,14 @@ class OracleDatabase(Database):
|
|
|
170
170
|
curs.execute(sql, params)
|
|
171
171
|
except ErrInfo:
|
|
172
172
|
raise
|
|
173
|
-
except Exception:
|
|
173
|
+
except Exception as e:
|
|
174
174
|
self.rollback()
|
|
175
175
|
raise ErrInfo(
|
|
176
176
|
type="db",
|
|
177
177
|
command_text=sql,
|
|
178
178
|
exception_msg=exception_desc(),
|
|
179
179
|
other_msg=f"Failed test for existence of column {column_name} in table {table_name} of {self.name()}",
|
|
180
|
-
)
|
|
180
|
+
) from e
|
|
181
181
|
rows = curs.fetchall()
|
|
182
182
|
curs.close()
|
|
183
183
|
return len(rows) > 0
|
|
@@ -194,14 +194,14 @@ class OracleDatabase(Database):
|
|
|
194
194
|
curs.execute(sql, params)
|
|
195
195
|
except ErrInfo:
|
|
196
196
|
raise
|
|
197
|
-
except Exception:
|
|
197
|
+
except Exception as e:
|
|
198
198
|
self.rollback()
|
|
199
199
|
raise ErrInfo(
|
|
200
200
|
type="db",
|
|
201
201
|
command_text=sql,
|
|
202
202
|
exception_msg=exception_desc(),
|
|
203
203
|
other_msg=f"Failed to get column names for table {table_name} of {self.name()}",
|
|
204
|
-
)
|
|
204
|
+
) from e
|
|
205
205
|
rows = curs.fetchall()
|
|
206
206
|
curs.close()
|
|
207
207
|
return [row[0] for row in rows]
|
|
@@ -218,14 +218,14 @@ class OracleDatabase(Database):
|
|
|
218
218
|
curs.execute(sql, params)
|
|
219
219
|
except ErrInfo:
|
|
220
220
|
raise
|
|
221
|
-
except Exception:
|
|
221
|
+
except Exception as e:
|
|
222
222
|
self.rollback()
|
|
223
223
|
raise ErrInfo(
|
|
224
224
|
type="db",
|
|
225
225
|
command_text=sql,
|
|
226
226
|
exception_msg=exception_desc(),
|
|
227
227
|
other_msg=f"Failed test for existence of view {view_name} in {self.name()}",
|
|
228
|
-
)
|
|
228
|
+
) from e
|
|
229
229
|
rows = curs.fetchall()
|
|
230
230
|
curs.close()
|
|
231
231
|
return len(rows) > 0
|
execsql/db/postgres.py
CHANGED
|
@@ -87,18 +87,20 @@ class PostgresDatabase(Database):
|
|
|
87
87
|
port=db.port,
|
|
88
88
|
connect_timeout=db.connect_timeout,
|
|
89
89
|
)
|
|
90
|
-
except Exception:
|
|
90
|
+
except Exception as e:
|
|
91
91
|
msg = (
|
|
92
92
|
f"Failed to open PostgreSQL database {self.db_name} on {self.server_name}; "
|
|
93
93
|
"check server and database name, and validity of credentials"
|
|
94
94
|
)
|
|
95
|
-
raise ErrInfo(type="exception", exception_msg=exception_desc(), other_msg=msg)
|
|
95
|
+
raise ErrInfo(type="exception", exception_msg=exception_desc(), other_msg=msg) from e
|
|
96
96
|
|
|
97
97
|
def create_db(db: PostgresDatabase) -> None:
|
|
98
98
|
conn = db_conn(db, "postgres")
|
|
99
99
|
conn.autocommit = True
|
|
100
100
|
curs = conn.cursor()
|
|
101
|
-
|
|
101
|
+
quoted_name = db.quote_identifier(db.db_name)
|
|
102
|
+
quoted_enc = db.quote_identifier(db.encoding)
|
|
103
|
+
curs.execute(f"create database {quoted_name} encoding {quoted_enc};")
|
|
102
104
|
conn.close()
|
|
103
105
|
|
|
104
106
|
if self.conn is None:
|
|
@@ -133,9 +135,9 @@ class PostgresDatabase(Database):
|
|
|
133
135
|
raise
|
|
134
136
|
except ErrInfo:
|
|
135
137
|
raise
|
|
136
|
-
except Exception:
|
|
138
|
+
except Exception as e:
|
|
137
139
|
msg = f"Failed to open PostgreSQL database {self.db_name} on {self.server_name}"
|
|
138
|
-
raise ErrInfo(type="exception", exception_msg=exception_desc(), other_msg=msg)
|
|
140
|
+
raise ErrInfo(type="exception", exception_msg=exception_desc(), other_msg=msg) from e
|
|
139
141
|
# (Re)set the encoding to match the database.
|
|
140
142
|
self.encoding = self.conn.encoding
|
|
141
143
|
|
|
@@ -177,14 +179,14 @@ class PostgresDatabase(Database):
|
|
|
177
179
|
curs.execute(sql, params)
|
|
178
180
|
except ErrInfo:
|
|
179
181
|
raise
|
|
180
|
-
except Exception:
|
|
182
|
+
except Exception as e:
|
|
181
183
|
self.rollback()
|
|
182
184
|
raise ErrInfo(
|
|
183
185
|
type="db",
|
|
184
186
|
command_text=sql,
|
|
185
187
|
exception_msg=exception_desc(),
|
|
186
188
|
other_msg=f"Failed test for existence of table {table_name} in {self.name()}",
|
|
187
|
-
)
|
|
189
|
+
) from e
|
|
188
190
|
rows = curs.fetchall()
|
|
189
191
|
curs.close()
|
|
190
192
|
return len(rows) > 0
|
|
@@ -209,14 +211,14 @@ class PostgresDatabase(Database):
|
|
|
209
211
|
curs.execute(sql, params)
|
|
210
212
|
except ErrInfo:
|
|
211
213
|
raise
|
|
212
|
-
except Exception:
|
|
214
|
+
except Exception as e:
|
|
213
215
|
self.rollback()
|
|
214
216
|
raise ErrInfo(
|
|
215
217
|
type="db",
|
|
216
218
|
command_text=sql,
|
|
217
219
|
exception_msg=exception_desc(),
|
|
218
220
|
other_msg=f"Failed test for existence of view {view_name} in {self.name()}",
|
|
219
|
-
)
|
|
221
|
+
) from e
|
|
220
222
|
rows = curs.fetchall()
|
|
221
223
|
curs.close()
|
|
222
224
|
return len(rows) > 0
|
|
@@ -304,9 +306,18 @@ class PostgresDatabase(Database):
|
|
|
304
306
|
# ASCII unit separator, which, if it had been used for its intended purpose,
|
|
305
307
|
# should have been identified as the delimiter, so presumably it has not been used.
|
|
306
308
|
delim = csv_file_obj.delimiter if csv_file_obj.delimiter else chr(31)
|
|
307
|
-
|
|
309
|
+
if len(delim) != 1:
|
|
310
|
+
raise ErrInfo(
|
|
311
|
+
type="error",
|
|
312
|
+
other_msg=f"Invalid delimiter for COPY: expected single character, got {len(delim)} characters",
|
|
313
|
+
)
|
|
314
|
+
safe_delim = delim.replace("'", "''")
|
|
315
|
+
copy_cmd = (
|
|
316
|
+
f"copy {sq_name} ({input_col_list}) from stdin with (format csv, null '', delimiter '{safe_delim}'"
|
|
317
|
+
)
|
|
308
318
|
if csv_file_obj.quotechar:
|
|
309
|
-
|
|
319
|
+
safe_quote = csv_file_obj.quotechar.replace("'", "''")
|
|
320
|
+
copy_cmd = copy_cmd + f", quote '{safe_quote}'"
|
|
310
321
|
copy_cmd = copy_cmd + ")"
|
|
311
322
|
_state.exec_log.log_status_info(
|
|
312
323
|
f"IMPORTing {csv_file_obj.csvfname} using Postgres' fast file reading routine",
|
|
@@ -315,13 +326,13 @@ class PostgresDatabase(Database):
|
|
|
315
326
|
curs.copy_expert(copy_cmd, rf, _state.conf.import_buffer)
|
|
316
327
|
except ErrInfo:
|
|
317
328
|
raise
|
|
318
|
-
except Exception:
|
|
329
|
+
except Exception as e:
|
|
319
330
|
self.rollback()
|
|
320
331
|
raise ErrInfo(
|
|
321
332
|
type="exception",
|
|
322
333
|
exception_msg=exception_desc(),
|
|
323
334
|
other_msg=f"Can't import from file to table {sq_name}",
|
|
324
|
-
)
|
|
335
|
+
) from e
|
|
325
336
|
else:
|
|
326
337
|
data_indexes = [csv_file_cols_q.index(col) for col in import_cols]
|
|
327
338
|
paramspec = ",".join(["%s"] * len(import_cols))
|
|
@@ -398,14 +409,14 @@ class PostgresDatabase(Database):
|
|
|
398
409
|
curs.executemany(sql_template, b)
|
|
399
410
|
except ErrInfo:
|
|
400
411
|
raise
|
|
401
|
-
except Exception:
|
|
412
|
+
except Exception as e:
|
|
402
413
|
self.rollback()
|
|
403
414
|
raise ErrInfo(
|
|
404
415
|
type="db",
|
|
405
416
|
command_text=sql_template,
|
|
406
417
|
exception_msg=exception_desc(),
|
|
407
418
|
other_msg=f"Can't load data into table {sq_name} of {self.name()} from line {{{line}}}",
|
|
408
|
-
)
|
|
419
|
+
) from e
|
|
409
420
|
total_rows += len(b)
|
|
410
421
|
interval = _state.conf.import_progress_interval
|
|
411
422
|
if _state.exec_log and interval > 0 and total_rows % interval == 0:
|
|
@@ -431,5 +442,6 @@ class PostgresDatabase(Database):
|
|
|
431
442
|
with open(file_name, "rb") as f:
|
|
432
443
|
filedata = f.read()
|
|
433
444
|
sq_name = self.schema_qualified_table_name(schema_name, table_name)
|
|
434
|
-
|
|
445
|
+
quoted_col = self.quote_identifier(column_name)
|
|
446
|
+
sql = f"insert into {sq_name} ({quoted_col}) values ({self.paramsubs(1)});"
|
|
435
447
|
self.cursor().execute(sql, (psycopg2.Binary(filedata),))
|
execsql/db/sqlite.py
CHANGED
|
@@ -54,12 +54,12 @@ class SQLiteDatabase(Database):
|
|
|
54
54
|
self.conn = sqlite3.connect(self.db_name, timeout=self.timeout)
|
|
55
55
|
except ErrInfo:
|
|
56
56
|
raise
|
|
57
|
-
except Exception:
|
|
57
|
+
except Exception as e:
|
|
58
58
|
raise ErrInfo(
|
|
59
59
|
type="exception",
|
|
60
60
|
exception_msg=exception_desc(),
|
|
61
61
|
other_msg=f"Can't open SQLite database {self.db_name}",
|
|
62
|
-
)
|
|
62
|
+
) from e
|
|
63
63
|
pragma_cols, pragma_data = self.select_data("pragma encoding;")
|
|
64
64
|
self.encoding = pragma_data[0][0]
|
|
65
65
|
|
|
@@ -82,14 +82,14 @@ class SQLiteDatabase(Database):
|
|
|
82
82
|
curs.execute(sql, (table_name,))
|
|
83
83
|
except ErrInfo:
|
|
84
84
|
raise
|
|
85
|
-
except Exception:
|
|
85
|
+
except Exception as e:
|
|
86
86
|
self.rollback()
|
|
87
87
|
raise ErrInfo(
|
|
88
88
|
type="db",
|
|
89
89
|
command_text=sql,
|
|
90
90
|
exception_msg=exception_desc(),
|
|
91
91
|
other_msg=f'Failed test for existence of SQLite table "{table_name}";',
|
|
92
|
-
)
|
|
92
|
+
) from e
|
|
93
93
|
rows = curs.fetchall()
|
|
94
94
|
return len(rows) > 0
|
|
95
95
|
|
|
@@ -110,14 +110,14 @@ class SQLiteDatabase(Database):
|
|
|
110
110
|
curs.execute(sql)
|
|
111
111
|
except ErrInfo:
|
|
112
112
|
raise
|
|
113
|
-
except Exception:
|
|
113
|
+
except Exception as e:
|
|
114
114
|
self.rollback()
|
|
115
115
|
raise ErrInfo(
|
|
116
116
|
type="db",
|
|
117
117
|
command_text=sql,
|
|
118
118
|
exception_msg=exception_desc(),
|
|
119
119
|
other_msg=f"Failed to get column names for table {table_name} of {self.name()}",
|
|
120
|
-
)
|
|
120
|
+
) from e
|
|
121
121
|
return [d[0] for d in curs.description]
|
|
122
122
|
|
|
123
123
|
def view_exists(self, view_name: str) -> bool:
|
|
@@ -127,14 +127,14 @@ class SQLiteDatabase(Database):
|
|
|
127
127
|
curs.execute(sql, (view_name,))
|
|
128
128
|
except ErrInfo:
|
|
129
129
|
raise
|
|
130
|
-
except Exception:
|
|
130
|
+
except Exception as e:
|
|
131
131
|
self.rollback()
|
|
132
132
|
raise ErrInfo(
|
|
133
133
|
type="db",
|
|
134
134
|
command_text=sql,
|
|
135
135
|
exception_msg=exception_desc(),
|
|
136
136
|
other_msg=f'Failed test for existence of SQLite view "{view_name}";',
|
|
137
|
-
)
|
|
137
|
+
) from e
|
|
138
138
|
rows = curs.fetchall()
|
|
139
139
|
return len(rows) > 0
|
|
140
140
|
|
|
@@ -204,14 +204,14 @@ class SQLiteDatabase(Database):
|
|
|
204
204
|
curs.execute(sql, linedata)
|
|
205
205
|
except ErrInfo:
|
|
206
206
|
raise
|
|
207
|
-
except Exception:
|
|
207
|
+
except Exception as e:
|
|
208
208
|
self.rollback()
|
|
209
209
|
raise ErrInfo(
|
|
210
210
|
type="db",
|
|
211
211
|
command_text=sql,
|
|
212
212
|
exception_msg=exception_desc(),
|
|
213
213
|
other_msg=f"Can't load data into table {sq_name} from line {{{line}}}",
|
|
214
|
-
)
|
|
214
|
+
) from e
|
|
215
215
|
total_rows += 1
|
|
216
216
|
interval = getattr(_state.conf, "import_progress_interval", 0)
|
|
217
217
|
if _state.exec_log and interval > 0 and total_rows % interval == 0:
|
|
@@ -235,5 +235,6 @@ class SQLiteDatabase(Database):
|
|
|
235
235
|
with open(file_name, "rb") as f:
|
|
236
236
|
filedata = f.read()
|
|
237
237
|
sq_name = self.schema_qualified_table_name(schema_name, table_name)
|
|
238
|
-
|
|
238
|
+
quoted_col = self.quote_identifier(column_name)
|
|
239
|
+
sql = f"insert into {sq_name} ({quoted_col}) values ({self.paramsubs(1)});"
|
|
239
240
|
self.cursor().execute(sql, (sqlite3.Binary(filedata),))
|
execsql/db/sqlserver.py
CHANGED
|
@@ -141,7 +141,7 @@ class SqlServerDatabase(Database):
|
|
|
141
141
|
|
|
142
142
|
def schema_exists(self, schema_name: str) -> bool:
|
|
143
143
|
curs = self.cursor()
|
|
144
|
-
curs.execute(
|
|
144
|
+
curs.execute("select * from sys.schemas where name = ?;", (schema_name,))
|
|
145
145
|
rows = curs.fetchall()
|
|
146
146
|
curs.close()
|
|
147
147
|
return len(rows) > 0
|
|
@@ -149,7 +149,8 @@ class SqlServerDatabase(Database):
|
|
|
149
149
|
def role_exists(self, rolename: str) -> bool:
|
|
150
150
|
curs = self.cursor()
|
|
151
151
|
curs.execute(
|
|
152
|
-
|
|
152
|
+
"select name from sys.database_principals where type in ('R', 'S') and name = ?;",
|
|
153
|
+
(rolename,),
|
|
153
154
|
)
|
|
154
155
|
rows = curs.fetchall()
|
|
155
156
|
curs.close()
|
|
@@ -172,5 +173,6 @@ class SqlServerDatabase(Database):
|
|
|
172
173
|
with open(file_name, "rb") as f:
|
|
173
174
|
filedata = f.read()
|
|
174
175
|
sq_name = self.schema_qualified_table_name(schema_name, table_name)
|
|
175
|
-
|
|
176
|
+
quoted_col = self.quote_identifier(column_name)
|
|
177
|
+
sql = f"insert into {sq_name} ({quoted_col}) values ({self.paramsubs(1)});"
|
|
176
178
|
self.cursor().execute(sql, (pyodbc.Binary(filedata),))
|
execsql/exceptions.py
CHANGED
|
@@ -58,7 +58,7 @@ class ExecSqlTimeoutError(ExecSqlError):
|
|
|
58
58
|
super().__init__(errmsg)
|
|
59
59
|
|
|
60
60
|
|
|
61
|
-
class ErrInfo(
|
|
61
|
+
class ErrInfo(ExecSqlError):
|
|
62
62
|
"""Rich exception and error-data carrier for execsql.
|
|
63
63
|
|
|
64
64
|
``str(e)`` returns the most informative available message (``other_msg``,
|
|
@@ -152,11 +152,11 @@ class ErrInfo(Exception):
|
|
|
152
152
|
return self.eval_err()
|
|
153
153
|
|
|
154
154
|
|
|
155
|
-
class DataTypeError(
|
|
155
|
+
class DataTypeError(ExecSqlError):
|
|
156
156
|
def __init__(self, data_type_name: str, error_msg: str) -> None:
|
|
157
157
|
self.data_type_name = data_type_name or "Unspecified data type"
|
|
158
158
|
self.error_msg = error_msg or "Unspecified error"
|
|
159
|
-
|
|
159
|
+
Exception.__init__(self, str(self))
|
|
160
160
|
|
|
161
161
|
def __repr__(self) -> str:
|
|
162
162
|
return f"DataTypeError({self.data_type_name!r}, {self.error_msg!r})"
|
|
@@ -165,12 +165,12 @@ class DataTypeError(Exception):
|
|
|
165
165
|
return f"{self.data_type_name}: {self.error_msg}"
|
|
166
166
|
|
|
167
167
|
|
|
168
|
-
class DbTypeError(
|
|
168
|
+
class DbTypeError(ExecSqlError):
|
|
169
169
|
def __init__(self, dbms_id: str, data_type: object, error_msg: str) -> None:
|
|
170
170
|
self.dbms_id = dbms_id
|
|
171
171
|
self.data_type = data_type
|
|
172
172
|
self.error_msg = error_msg or "Unspecified error"
|
|
173
|
-
|
|
173
|
+
Exception.__init__(self, str(self))
|
|
174
174
|
|
|
175
175
|
def __repr__(self) -> str:
|
|
176
176
|
return f"DbTypeError({self.dbms_id!r}, {self.data_type!r}, {self.error_msg!r})"
|
|
@@ -190,11 +190,11 @@ class DataTableError(ExecSqlError):
|
|
|
190
190
|
"""Raised for DataTable-level errors."""
|
|
191
191
|
|
|
192
192
|
|
|
193
|
-
class DatabaseNotImplementedError(
|
|
193
|
+
class DatabaseNotImplementedError(ExecSqlError):
|
|
194
194
|
def __init__(self, db_name: str, method: str) -> None:
|
|
195
195
|
self.db_name = db_name
|
|
196
196
|
self.method = method
|
|
197
|
-
|
|
197
|
+
Exception.__init__(self, str(self))
|
|
198
198
|
|
|
199
199
|
def __repr__(self) -> str:
|
|
200
200
|
return f"DatabaseNotImplementedError({self.db_name!r}, {self.method!r})"
|
execsql/exporters/base.py
CHANGED
|
@@ -143,7 +143,12 @@ class WriteSpec:
|
|
|
143
143
|
if self.outfile:
|
|
144
144
|
from execsql.utils.fileio import EncodedFile
|
|
145
145
|
|
|
146
|
-
EncodedFile(self.outfile, conf.output_encoding)
|
|
146
|
+
ef = EncodedFile(self.outfile, conf.output_encoding)
|
|
147
|
+
fh = ef.open("a")
|
|
148
|
+
try:
|
|
149
|
+
fh.write(msg)
|
|
150
|
+
finally:
|
|
151
|
+
fh.close()
|
|
147
152
|
if (not self.outfile) or self.tee:
|
|
148
153
|
try:
|
|
149
154
|
_state.output.write(msg.encode(conf.output_encoding))
|
execsql/exporters/delimited.py
CHANGED
|
@@ -415,7 +415,11 @@ class CsvFile(EncodedFile):
|
|
|
415
415
|
def evaluate_line_format(self) -> None:
|
|
416
416
|
# Scans the file to determine the delimiter, quote character, and escapechar.
|
|
417
417
|
if not self.lineformat_set:
|
|
418
|
-
|
|
418
|
+
f = self.openclean("rt")
|
|
419
|
+
try:
|
|
420
|
+
self.delimiter, self.quotechar, self.escapechar = self.diagnose_delim(f)
|
|
421
|
+
finally:
|
|
422
|
+
f.close()
|
|
419
423
|
self.lineformat_set = True
|
|
420
424
|
|
|
421
425
|
def _record_format_error(self, pos_no: int, errmsg: str) -> None:
|
|
@@ -571,24 +575,26 @@ class CsvFile(EncodedFile):
|
|
|
571
575
|
self.evaluate_line_format()
|
|
572
576
|
f = self.openclean("rt")
|
|
573
577
|
line_no = 0
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
if
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
578
|
+
try:
|
|
579
|
+
while True:
|
|
580
|
+
line_no += 1
|
|
581
|
+
try:
|
|
582
|
+
elements = self.read_and_parse_line(f)
|
|
583
|
+
except ErrInfo as e:
|
|
584
|
+
raise ErrInfo("error", other_msg=f"{e.other} on line {line_no}.") from e
|
|
585
|
+
except:
|
|
586
|
+
raise
|
|
587
|
+
if len(elements) > 0:
|
|
588
|
+
if conf.del_empty_cols and len(self.blank_cols) > 0:
|
|
589
|
+
blanks = copy.copy(self.blank_cols)
|
|
590
|
+
while len(blanks) > 0:
|
|
591
|
+
b = blanks.pop()
|
|
592
|
+
del elements[b]
|
|
593
|
+
yield elements
|
|
594
|
+
else:
|
|
595
|
+
break
|
|
596
|
+
finally:
|
|
597
|
+
f.close()
|
|
592
598
|
|
|
593
599
|
def writer(self, append: bool = False) -> CsvWriter:
|
|
594
600
|
return CsvWriter(self.filename, self.encoding, self.delimiter, self.quotechar, self.escapechar, append)
|
|
@@ -600,12 +606,12 @@ class CsvFile(EncodedFile):
|
|
|
600
606
|
except ErrInfo as e:
|
|
601
607
|
e.other = f"Can't read column header line from {self.filename}. {e.other or ''}"
|
|
602
608
|
raise
|
|
603
|
-
except Exception:
|
|
609
|
+
except Exception as e:
|
|
604
610
|
raise ErrInfo(
|
|
605
611
|
type="exception",
|
|
606
612
|
exception_msg=exception_desc(),
|
|
607
613
|
other_msg=f"Can't read column header line from {self.filename}",
|
|
608
|
-
)
|
|
614
|
+
) from e
|
|
609
615
|
if any(x is None or len(x) == 0 for x in colnames):
|
|
610
616
|
if conf.del_empty_cols:
|
|
611
617
|
self.blank_cols = [
|
|
@@ -700,18 +706,21 @@ def write_delimited_file(
|
|
|
700
706
|
filewriter_close(outfile)
|
|
701
707
|
ofile = EncodedFile(outfile, file_encoding).open(mode=fmode)
|
|
702
708
|
fdesc = outfile
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
for rec in rowsource:
|
|
707
|
-
try:
|
|
708
|
-
datarow = line_delimiter.delimited(rec)
|
|
709
|
+
try:
|
|
710
|
+
if not (filefmt.lower() == "plain" or (append and zipfile is None)):
|
|
711
|
+
datarow = line_delimiter.delimited(column_headers)
|
|
709
712
|
ofile.write(datarow)
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
713
|
+
for rec in rowsource:
|
|
714
|
+
try:
|
|
715
|
+
datarow = line_delimiter.delimited(rec)
|
|
716
|
+
ofile.write(datarow)
|
|
717
|
+
except ErrInfo:
|
|
718
|
+
raise
|
|
719
|
+
except Exception as e:
|
|
720
|
+
raise ErrInfo(
|
|
721
|
+
"exception",
|
|
722
|
+
exception_msg=exception_desc(),
|
|
723
|
+
other_msg=f"Can't write output to file {fdesc}.",
|
|
724
|
+
) from e
|
|
725
|
+
finally:
|
|
726
|
+
ofile.close()
|
execsql/exporters/duckdb.py
CHANGED
|
@@ -85,6 +85,6 @@ def write_query_to_duckdb(
|
|
|
85
85
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
86
86
|
except ErrInfo:
|
|
87
87
|
raise
|
|
88
|
-
except Exception:
|
|
89
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
88
|
+
except Exception as e:
|
|
89
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
90
90
|
export_duckdb(outfile, hdrs, rows, append, tablename)
|
execsql/exporters/feather.py
CHANGED
|
@@ -22,12 +22,12 @@ from execsql.utils.fileio import filewriter_close
|
|
|
22
22
|
def write_query_to_feather(outfile: str, headers: list[str], rows: Any) -> None:
|
|
23
23
|
try:
|
|
24
24
|
import polars as pl
|
|
25
|
-
except ImportError:
|
|
25
|
+
except ImportError as e:
|
|
26
26
|
raise ErrInfo(
|
|
27
27
|
"exception",
|
|
28
28
|
exception_msg=exception_desc(),
|
|
29
29
|
other_msg="The polars Python package must be installed to export data to the feather format.",
|
|
30
|
-
)
|
|
30
|
+
) from e
|
|
31
31
|
rows_list = list(rows)
|
|
32
32
|
if rows_list:
|
|
33
33
|
df = pl.DataFrame(rows_list, schema=headers, orient="row")
|
|
@@ -47,18 +47,18 @@ def write_query_to_hdf5(
|
|
|
47
47
|
) -> None:
|
|
48
48
|
try:
|
|
49
49
|
import tables
|
|
50
|
-
except ImportError:
|
|
50
|
+
except ImportError as e:
|
|
51
51
|
raise ErrInfo(
|
|
52
52
|
"exception",
|
|
53
53
|
exception_msg=exception_desc(),
|
|
54
54
|
other_msg="The tables Python library must be installed to export data to the HDF5 format.",
|
|
55
|
-
)
|
|
55
|
+
) from e
|
|
56
56
|
try:
|
|
57
57
|
hdrs, rows = db.select_rowsource(select_stmt)
|
|
58
58
|
except ErrInfo:
|
|
59
59
|
raise
|
|
60
|
-
except Exception:
|
|
61
|
-
raise ErrInfo("db", select_stmt, exception_msg=exception_desc())
|
|
60
|
+
except Exception as e:
|
|
61
|
+
raise ErrInfo("db", select_stmt, exception_msg=exception_desc()) from e
|
|
62
62
|
|
|
63
63
|
def h5type(datatype, size):
|
|
64
64
|
if datatype in (_state.DT_Varchar, _state.DT_Text):
|