execsql2 2.26.0__py3-none-any.whl → 2.28.0__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.
Files changed (62) hide show
  1. execsql/cli/commands/__init__.py +1 -0
  2. execsql/cli/commands/format.py +2 -1
  3. execsql/cli/commands/listing.py +32 -5
  4. execsql/cli/commands/lsp.py +64 -0
  5. execsql/cli/dispatch.py +1 -1
  6. execsql/cli/help.py +44 -96
  7. execsql/cli/lint.py +58 -22
  8. execsql/db/access.py +4 -4
  9. execsql/db/base.py +17 -3
  10. execsql/db/dsn.py +2 -2
  11. execsql/db/duckdb.py +14 -2
  12. execsql/db/firebird.py +2 -3
  13. execsql/db/mysql.py +2 -2
  14. execsql/db/oracle.py +5 -5
  15. execsql/db/postgres.py +2 -3
  16. execsql/db/sqlserver.py +2 -2
  17. execsql/exporters/duckdb.py +2 -2
  18. execsql/exporters/ods.py +2 -2
  19. execsql/exporters/templates.py +2 -2
  20. execsql/exporters/xls.py +3 -3
  21. execsql/exporters/xlsx.py +2 -2
  22. execsql/exporters/yaml.py +3 -4
  23. execsql/format.py +71 -21
  24. execsql/importers/base.py +6 -10
  25. execsql/lsp/__init__.py +6 -0
  26. execsql/lsp/code_actions.py +217 -0
  27. execsql/lsp/completion.py +203 -0
  28. execsql/lsp/diagnostics.py +66 -0
  29. execsql/lsp/document.py +210 -0
  30. execsql/lsp/folding.py +59 -0
  31. execsql/lsp/formatting.py +37 -0
  32. execsql/lsp/hover.py +119 -0
  33. execsql/lsp/navigation.py +169 -0
  34. execsql/lsp/rename.py +91 -0
  35. execsql/lsp/server.py +358 -0
  36. execsql/metacommands/debug.py +2 -2
  37. execsql/metacommands/dispatch.py +7 -0
  38. execsql/metacommands/io_fileops.py +6 -6
  39. execsql/metacommands/reference.py +1404 -0
  40. execsql/metacommands/upsert.py +2 -5
  41. execsql/models.py +11 -0
  42. execsql/utils/errors.py +16 -0
  43. {execsql2-2.26.0.dist-info → execsql2-2.28.0.dist-info}/METADATA +34 -18
  44. {execsql2-2.26.0.dist-info → execsql2-2.28.0.dist-info}/RECORD +62 -49
  45. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/README.md +0 -0
  46. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/config_settings.sqlite +0 -0
  47. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/example_config_prompt.sql +0 -0
  48. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/make_config_db.sql +0 -0
  49. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/md_compare.sql +0 -0
  50. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/md_glossary.sql +0 -0
  51. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/md_upsert.sql +0 -0
  52. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/pg_compare.sql +0 -0
  53. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/pg_glossary.sql +0 -0
  54. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/pg_upsert.sql +0 -0
  55. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/script_template.sql +0 -0
  56. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/ss_compare.sql +0 -0
  57. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/ss_glossary.sql +0 -0
  58. {execsql2-2.26.0.data → execsql2-2.28.0.data}/data/execsql2_extras/ss_upsert.sql +0 -0
  59. {execsql2-2.26.0.dist-info → execsql2-2.28.0.dist-info}/WHEEL +0 -0
  60. {execsql2-2.26.0.dist-info → execsql2-2.28.0.dist-info}/entry_points.txt +0 -0
  61. {execsql2-2.26.0.dist-info → execsql2-2.28.0.dist-info}/licenses/LICENSE.txt +0 -0
  62. {execsql2-2.26.0.dist-info → execsql2-2.28.0.dist-info}/licenses/NOTICE +0 -0
execsql/lsp/server.py ADDED
@@ -0,0 +1,358 @@
1
+ """The pygls server behind ``execsql lsp``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import logging
7
+ import os
8
+ from dataclasses import dataclass, field
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ from lsprotocol import types
13
+ from pygls.lsp.server import LanguageServer
14
+ from pygls.uris import to_fs_path
15
+
16
+ from execsql import __version__
17
+ from execsql.lsp.code_actions import code_actions
18
+ from execsql.lsp.completion import completions
19
+ from execsql.lsp.diagnostics import diagnostics, too_deep_diagnostic
20
+ from execsql.lsp.document import ScriptIndex, index_script, read_included, same_file_key
21
+ from execsql.lsp.folding import folding_ranges
22
+ from execsql.lsp.formatting import format_document
23
+ from execsql.lsp.hover import hover
24
+ from execsql.lsp.rename import RenameError, prepare_rename, rename
25
+ from execsql.lsp.navigation import definition, document_links, document_symbols, references
26
+
27
+ __all__ = ["ExecsqlLanguageServer", "create_server"]
28
+
29
+ # Pause after the last keystroke before linting, so a burst of typing lints once.
30
+ LINT_DELAY_SECONDS = 0.2
31
+
32
+ # Files whose change can alter an open script's findings or index: the config
33
+ # file (its [lint] and [format] sections) and any script it may INCLUDE.
34
+ WATCHED_FILES = ("**/execsql.conf", "**/*.sql")
35
+
36
+
37
+ class _DropLateCancels(logging.Filter):
38
+ """Drops pygls's warning about a cancel for a request already answered.
39
+
40
+ Editors cancel requests they no longer need (a hover after the cursor
41
+ moves); the handlers answer in milliseconds, so the cancel usually
42
+ arrives late. That is normal, but the warning reaches the editor's
43
+ output as an error.
44
+ """
45
+
46
+ def filter(self, record: logging.LogRecord) -> bool:
47
+ return not record.getMessage().startswith("Cancel notification for unknown message id")
48
+
49
+
50
+ @dataclass
51
+ class Settings:
52
+ """What the configuration sets for one workspace folder: lint rule selection and format options."""
53
+
54
+ select: tuple[str, ...] = ()
55
+ ignore: tuple[str, ...] = ()
56
+ format_options: dict[str, Any] = field(default_factory=dict) # format_document keywords, from [format]
57
+
58
+
59
+ class ExecsqlLanguageServer(LanguageServer):
60
+ """Holds what the handlers share: each workspace folder's settings and pending lint tasks."""
61
+
62
+ def __init__(self) -> None:
63
+ super().__init__("execsql", __version__)
64
+ self.settings: dict[str, Settings] = {} # by same_file_key of each workspace folder
65
+ self.fallback = Settings() # for a file in no workspace folder
66
+ self.pending: dict[str, asyncio.Task[Any]] = {}
67
+
68
+ def load_settings(self) -> None:
69
+ """Read ``[lint]`` and ``[format]`` for each workspace folder, as ``execsql lint`` / ``execsql format`` run there would.
70
+
71
+ execsql reads config files from the working directory, so each
72
+ folder's settings are read from inside it. The server itself works
73
+ from the workspace root: a file in no folder gets the settings read
74
+ there, and an unsaved script's relative ``INCLUDE`` resolves there.
75
+ """
76
+ root = self.workspace.root_path
77
+ if root and os.path.isdir(root):
78
+ os.chdir(root)
79
+ self.fallback = self._read_settings(None)
80
+ self.settings = {}
81
+ for folder in self.workspace.folders.values():
82
+ path = to_fs_path(folder.uri)
83
+ if path and os.path.isdir(path):
84
+ self.settings[same_file_key(path)] = self._read_settings(path)
85
+
86
+ def _read_settings(self, folder: str | None) -> Settings:
87
+ """The settings ``execsql lint`` run from *folder* (the working directory if ``None``) reads.
88
+
89
+ A config error is shown to the user and leaves every rule on.
90
+ """
91
+ from execsql.cli.lint import resolve_selectors
92
+ from execsql.cli.run import _tool_config
93
+
94
+ here = os.getcwd()
95
+ try:
96
+ if folder is not None:
97
+ os.chdir(folder)
98
+ conf = _tool_config(None)
99
+ return Settings(
100
+ select=resolve_selectors([conf.lint_select]) if conf.lint_select else (),
101
+ ignore=resolve_selectors([conf.lint_ignore]) if conf.lint_ignore else (),
102
+ format_options={
103
+ "indent": conf.format_indent,
104
+ "use_sql": conf.format_sql,
105
+ "leading_comma": conf.format_leading_comma,
106
+ "rewrite_sql": conf.format_rewrite_sql,
107
+ },
108
+ )
109
+ except Exception as exc: # a bad config must not take the server down
110
+ self.window_show_message(
111
+ types.ShowMessageParams(type=types.MessageType.Warning, message=f"execsql config: {exc}"),
112
+ )
113
+ return Settings()
114
+ finally:
115
+ os.chdir(here)
116
+
117
+ def settings_for(self, uri: str) -> Settings:
118
+ """The settings of the innermost workspace folder holding *uri*'s file, else :attr:`fallback`."""
119
+ path = to_fs_path(uri)
120
+ if path is None:
121
+ return self.fallback
122
+ key = same_file_key(path)
123
+ holding = [f for f in self.settings if key == f or key.startswith(f.rstrip(os.sep) + os.sep)]
124
+ return self.settings[max(holding, key=len)] if holding else self.fallback
125
+
126
+ def index(self, uri: str) -> ScriptIndex:
127
+ """The current document's index (rebuilt per request: a few milliseconds).
128
+
129
+ A script the parser cannot handle (nesting past Python's recursion
130
+ limit) gets an empty index, so features fall back to what needs none.
131
+ """
132
+ document = self.workspace.get_text_document(uri)
133
+ path = to_fs_path(uri)
134
+ try:
135
+ index = index_script(document.source, path, self.read_included)
136
+ except RecursionError:
137
+ index = ScriptIndex(path=path)
138
+ index.open_uris = self.open_uris()
139
+ return index
140
+
141
+ def open_uris(self) -> dict[str, str]:
142
+ """The open documents' URIs as the client spelled them, by :func:`same_file_key` of their paths.
143
+
144
+ A location in another open file is reported under this URI: one
145
+ rebuilt from the path can differ (``file:///c:/``, ``file:///C:/`` on
146
+ Windows), and a client may not match the two.
147
+ """
148
+ found: dict[str, str] = {}
149
+ for uri in self.workspace.text_documents:
150
+ fs_path = to_fs_path(uri)
151
+ if fs_path is not None:
152
+ found.setdefault(same_file_key(fs_path), uri)
153
+ return found
154
+
155
+ def read_included(self, path: Path) -> str | None:
156
+ """An ``INCLUDE``d file's text: the editor's copy if it is open (saved or not), else the file's.
157
+
158
+ Open documents are matched by path, not URI: clients spell the same
159
+ file differently (``file:///c%3A/``, ``file:///C:/`` on Windows).
160
+ """
161
+ uri = self.open_uris().get(same_file_key(str(path)))
162
+ if uri is not None:
163
+ return self.workspace.get_text_document(uri).source
164
+ return read_included(path)
165
+
166
+ def supports_snippets(self) -> bool:
167
+ caps = self.client_capabilities.text_document
168
+ item = caps.completion.completion_item if caps and caps.completion else None
169
+ return bool(item and item.snippet_support)
170
+
171
+ def publish(self, uri: str) -> None:
172
+ """Lint the document's current text and publish the result."""
173
+ document = self.workspace.get_text_document(uri)
174
+ settings = self.settings_for(uri)
175
+ try:
176
+ found = diagnostics(document.source, to_fs_path(uri), settings.select, settings.ignore)
177
+ except RecursionError:
178
+ found = [too_deep_diagnostic()]
179
+ self.text_document_publish_diagnostics(
180
+ types.PublishDiagnosticsParams(uri=uri, version=document.version, diagnostics=found),
181
+ )
182
+
183
+ def publish_all(self) -> None:
184
+ """Lint every open document again: a file one of them includes, or the config, changed."""
185
+ for uri in list(self.workspace.text_documents):
186
+ self.publish(uri)
187
+
188
+ def watch_files(self) -> None:
189
+ """Ask the client to report changes to :data:`WATCHED_FILES`, if it can."""
190
+ caps = self.client_capabilities.workspace
191
+ watched = caps.did_change_watched_files if caps else None
192
+ if not (watched and watched.dynamic_registration):
193
+ return
194
+ self.client_register_capability(
195
+ types.RegistrationParams(
196
+ registrations=[
197
+ types.Registration(
198
+ id="execsql-watched-files",
199
+ method=types.WORKSPACE_DID_CHANGE_WATCHED_FILES,
200
+ register_options=types.DidChangeWatchedFilesRegistrationOptions(
201
+ watchers=[types.FileSystemWatcher(glob_pattern=g) for g in WATCHED_FILES],
202
+ ),
203
+ ),
204
+ ],
205
+ ),
206
+ )
207
+
208
+ def publish_later(self, uri: str) -> None:
209
+ """Lint after :data:`LINT_DELAY_SECONDS` without another change."""
210
+ previous = self.pending.pop(uri, None)
211
+ if previous is not None:
212
+ previous.cancel()
213
+
214
+ async def run() -> None:
215
+ await asyncio.sleep(LINT_DELAY_SECONDS)
216
+ self.pending.pop(uri, None)
217
+ self.publish(uri)
218
+
219
+ self.pending[uri] = asyncio.ensure_future(run())
220
+
221
+
222
+ def create_server() -> ExecsqlLanguageServer:
223
+ """A server with every feature registered."""
224
+ server = ExecsqlLanguageServer()
225
+ rpc_logger = logging.getLogger("pygls.protocol.json_rpc")
226
+ if not any(isinstance(f, _DropLateCancels) for f in rpc_logger.filters):
227
+ rpc_logger.addFilter(_DropLateCancels())
228
+
229
+ @server.feature(types.INITIALIZED)
230
+ def initialized(ls: ExecsqlLanguageServer, params: types.InitializedParams) -> None:
231
+ ls.load_settings()
232
+ ls.watch_files()
233
+
234
+ @server.feature(types.TEXT_DOCUMENT_DID_OPEN)
235
+ def did_open(ls: ExecsqlLanguageServer, params: types.DidOpenTextDocumentParams) -> None:
236
+ ls.publish(params.text_document.uri)
237
+
238
+ @server.feature(types.TEXT_DOCUMENT_DID_CHANGE)
239
+ def did_change(ls: ExecsqlLanguageServer, params: types.DidChangeTextDocumentParams) -> None:
240
+ ls.publish_later(params.text_document.uri)
241
+
242
+ @server.feature(types.TEXT_DOCUMENT_DID_SAVE)
243
+ def did_save(ls: ExecsqlLanguageServer, params: types.DidSaveTextDocumentParams) -> None:
244
+ # Another open script may INCLUDE this one; a client that cannot watch files reports nothing else.
245
+ ls.publish_all()
246
+
247
+ @server.feature(types.WORKSPACE_DID_CHANGE_WATCHED_FILES)
248
+ def did_change_watched_files(ls: ExecsqlLanguageServer, params: types.DidChangeWatchedFilesParams) -> None:
249
+ if any(Path(to_fs_path(change.uri) or "").name.lower() == "execsql.conf" for change in params.changes):
250
+ ls.load_settings()
251
+ ls.publish_all()
252
+
253
+ @server.feature(types.WORKSPACE_DID_CHANGE_WORKSPACE_FOLDERS)
254
+ def did_change_folders(ls: ExecsqlLanguageServer, params: types.DidChangeWorkspaceFoldersParams) -> None:
255
+ # pygls has already updated ls.workspace.folders.
256
+ ls.load_settings()
257
+ ls.publish_all()
258
+
259
+ @server.feature(types.TEXT_DOCUMENT_DID_CLOSE)
260
+ def did_close(ls: ExecsqlLanguageServer, params: types.DidCloseTextDocumentParams) -> None:
261
+ uri = params.text_document.uri
262
+ pending = ls.pending.pop(uri, None)
263
+ if pending is not None:
264
+ pending.cancel()
265
+ ls.text_document_publish_diagnostics(types.PublishDiagnosticsParams(uri=uri, diagnostics=[]))
266
+
267
+ @server.feature(
268
+ types.TEXT_DOCUMENT_COMPLETION,
269
+ types.CompletionOptions(trigger_characters=["!", "(", "{", "$", " "]),
270
+ )
271
+ def completion(ls: ExecsqlLanguageServer, params: types.CompletionParams) -> types.CompletionList:
272
+ uri = params.text_document.uri
273
+ source = ls.workspace.get_text_document(uri).source
274
+ return completions(
275
+ ls.index(uri),
276
+ source,
277
+ params.position.line,
278
+ params.position.character,
279
+ snippets=ls.supports_snippets(),
280
+ )
281
+
282
+ @server.feature(types.TEXT_DOCUMENT_HOVER)
283
+ def on_hover(ls: ExecsqlLanguageServer, params: types.HoverParams) -> types.Hover | None:
284
+ uri = params.text_document.uri
285
+ source = ls.workspace.get_text_document(uri).source
286
+ return hover(ls.index(uri), source, params.position.line, params.position.character)
287
+
288
+ @server.feature(types.TEXT_DOCUMENT_DEFINITION)
289
+ def on_definition(ls: ExecsqlLanguageServer, params: types.DefinitionParams) -> list[types.Location]:
290
+ uri = params.text_document.uri
291
+ return definition(ls.index(uri), uri, params.position.line, params.position.character)
292
+
293
+ @server.feature(types.TEXT_DOCUMENT_REFERENCES)
294
+ def on_references(ls: ExecsqlLanguageServer, params: types.ReferenceParams) -> list[types.Location]:
295
+ uri = params.text_document.uri
296
+ return references(
297
+ ls.index(uri),
298
+ uri,
299
+ params.position.line,
300
+ params.position.character,
301
+ include_declaration=params.context.include_declaration,
302
+ )
303
+
304
+ @server.feature(types.TEXT_DOCUMENT_DOCUMENT_SYMBOL)
305
+ def on_symbols(ls: ExecsqlLanguageServer, params: types.DocumentSymbolParams) -> list[types.DocumentSymbol]:
306
+ uri = params.text_document.uri
307
+ try:
308
+ return document_symbols(ls.index(uri), ls.workspace.get_text_document(uri).source)
309
+ except RecursionError: # nesting deeper than the outline can walk: no outline
310
+ return []
311
+
312
+ @server.feature(
313
+ types.TEXT_DOCUMENT_CODE_ACTION,
314
+ types.CodeActionOptions(code_action_kinds=[types.CodeActionKind.QuickFix]),
315
+ )
316
+ def on_code_action(ls: ExecsqlLanguageServer, params: types.CodeActionParams) -> list[types.CodeAction]:
317
+ uri = params.text_document.uri
318
+ source = ls.workspace.get_text_document(uri).source
319
+ return code_actions(ls.index(uri), uri, source, params.context.diagnostics)
320
+
321
+ @server.feature(types.TEXT_DOCUMENT_FORMATTING)
322
+ def on_format(ls: ExecsqlLanguageServer, params: types.DocumentFormattingParams) -> list[types.TextEdit] | None:
323
+ uri = params.text_document.uri
324
+ source = ls.workspace.get_text_document(uri).source
325
+ try:
326
+ return format_document(source, **ls.settings_for(uri).format_options)
327
+ except Exception as exc: # e.g. the formatter extra missing: say so, change nothing
328
+ ls.window_show_message(
329
+ types.ShowMessageParams(type=types.MessageType.Error, message=f"execsql format: {exc}"),
330
+ )
331
+ return None
332
+
333
+ @server.feature(types.TEXT_DOCUMENT_PREPARE_RENAME)
334
+ def on_prepare_rename(ls: ExecsqlLanguageServer, params: types.PrepareRenameParams) -> types.Range | None:
335
+ return prepare_rename(ls.index(params.text_document.uri), params.position.line, params.position.character)
336
+
337
+ @server.feature(types.TEXT_DOCUMENT_RENAME, types.RenameOptions(prepare_provider=True))
338
+ def on_rename(ls: ExecsqlLanguageServer, params: types.RenameParams) -> types.WorkspaceEdit | None:
339
+ uri = params.text_document.uri
340
+ try:
341
+ return rename(ls.index(uri), uri, params.position.line, params.position.character, params.new_name)
342
+ except RenameError as exc:
343
+ ls.window_show_message(types.ShowMessageParams(type=types.MessageType.Warning, message=str(exc)))
344
+ return None
345
+
346
+ @server.feature(types.TEXT_DOCUMENT_FOLDING_RANGE)
347
+ def on_folding(ls: ExecsqlLanguageServer, params: types.FoldingRangeParams) -> list[types.FoldingRange]:
348
+ uri = params.text_document.uri
349
+ try:
350
+ return folding_ranges(ls.workspace.get_text_document(uri).source, to_fs_path(uri))
351
+ except RecursionError: # nesting deeper than the parser can walk: no folding
352
+ return []
353
+
354
+ @server.feature(types.TEXT_DOCUMENT_DOCUMENT_LINK)
355
+ def on_links(ls: ExecsqlLanguageServer, params: types.DocumentLinkParams) -> list[types.DocumentLink]:
356
+ return document_links(ls.index(params.text_document.uri))
357
+
358
+ return server
@@ -27,7 +27,7 @@ from pathlib import Path
27
27
  from typing import Any
28
28
 
29
29
  import execsql.state as _state
30
- from execsql.utils.errors import fatal_error
30
+ from execsql.utils.errors import fatal_error, install_hint
31
31
  from execsql.utils.fileio import EncodedFile, filewriter_open_as_new, filewriter_write
32
32
 
33
33
 
@@ -110,7 +110,7 @@ def x_debug_write_odbc_drivers(**kwargs: Any) -> None:
110
110
  try:
111
111
  import pyodbc
112
112
  except Exception:
113
- fatal_error("The pyodbc module is required.")
113
+ fatal_error(f"The pyodbc module is required to list ODBC drivers. {install_hint('odbc')}")
114
114
  output_dest = kwargs["filename"]
115
115
  append = kwargs["append"]
116
116
  if output_dest is not None and output_dest != "stdout" and append is None:
@@ -705,6 +705,13 @@ def build_dispatch_table() -> MetaCommandList:
705
705
  description="QUOTE_ALL_TEXT",
706
706
  category="config_option",
707
707
  )
708
+ # The documented form; upstream accepted only bare MAX_INT, which stays.
709
+ mcl.add(
710
+ r"^\s*CONFIG\s+MAX_INT\s+(?P<maxint>[0-9]+)\s*$",
711
+ x_max_int,
712
+ description="MAX_INT",
713
+ category="config_option",
714
+ )
708
715
  mcl.add(
709
716
  r"^\s*CONFIG\s+IMPORT_ROW_BUFFER\s+(?P<rows>[1-9][0-9]*)\s*$",
710
717
  x_import_row_buffer,
@@ -114,7 +114,7 @@ def x_copy(**kwargs: Any) -> None:
114
114
 
115
115
  if new_tbl2:
116
116
  try:
117
- hdrs, rows = db1.select_rowsource(select_stmt)
117
+ hdrs, rows = db1.select_rowsource(select_stmt, decode_bytes=False)
118
118
  except ErrInfo:
119
119
  raise
120
120
  except Exception as e:
@@ -142,7 +142,7 @@ def x_copy(**kwargs: Any) -> None:
142
142
  db2.execute("COMMIT;")
143
143
  else:
144
144
  try:
145
- hdrs, rows = db1.select_rowsource(select_stmt)
145
+ hdrs, rows = db1.select_rowsource(select_stmt, decode_bytes=False)
146
146
  except ErrInfo:
147
147
  raise
148
148
  except Exception as e:
@@ -153,7 +153,7 @@ def x_copy(**kwargs: Any) -> None:
153
153
  def get_ts() -> DataTable:
154
154
  nonlocal get_ts_tablespec
155
155
  if get_ts_tablespec is None:
156
- ts_hdrs, ts_rows = db1.select_rowsource(select_stmt)
156
+ ts_hdrs, ts_rows = db1.select_rowsource(select_stmt, decode_bytes=False)
157
157
  get_ts_tablespec = DataTable(ts_hdrs, ts_rows, infer_strings=False)
158
158
  return get_ts_tablespec
159
159
 
@@ -201,7 +201,7 @@ def x_copy_query(**kwargs: Any) -> None:
201
201
 
202
202
  if new_tbl2:
203
203
  try:
204
- hdrs, rows = db1.select_rowsource(select_stmt)
204
+ hdrs, rows = db1.select_rowsource(select_stmt, decode_bytes=False)
205
205
  except ErrInfo:
206
206
  raise
207
207
  except Exception as e:
@@ -229,7 +229,7 @@ def x_copy_query(**kwargs: Any) -> None:
229
229
  db2.execute("COMMIT;")
230
230
  else:
231
231
  try:
232
- hdrs, rows = db1.select_rowsource(select_stmt)
232
+ hdrs, rows = db1.select_rowsource(select_stmt, decode_bytes=False)
233
233
  except ErrInfo:
234
234
  raise
235
235
  except Exception as e:
@@ -240,7 +240,7 @@ def x_copy_query(**kwargs: Any) -> None:
240
240
  def get_ts() -> DataTable:
241
241
  nonlocal get_ts_tablespec
242
242
  if get_ts_tablespec is None:
243
- ts_hdrs, ts_rows = db1.select_rowsource(select_stmt)
243
+ ts_hdrs, ts_rows = db1.select_rowsource(select_stmt, decode_bytes=False)
244
244
  get_ts_tablespec = DataTable(ts_hdrs, ts_rows, infer_strings=False)
245
245
  return get_ts_tablespec
246
246