pytrilogy 0.0.3.110__py3-none-any.whl → 0.0.3.112__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.

Potentially problematic release.


This version of pytrilogy might be problematic. Click here for more details.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pytrilogy
3
- Version: 0.0.3.110
3
+ Version: 0.0.3.112
4
4
  Summary: Declarative, typed query language that compiles to SQL.
5
5
  Classifier: Programming Language :: Python
6
6
  Classifier: Programming Language :: Python :: 3
@@ -1,8 +1,8 @@
1
- pytrilogy-0.0.3.110.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
- trilogy/__init__.py,sha256=eQXK52ADInFKSjwAH2CwNZJ13e5H52e9Jr2wLo7cJkk,304
1
+ pytrilogy-0.0.3.112.dist-info/licenses/LICENSE.md,sha256=5ZRvtTyCCFwz1THxDTjAu3Lidds9WjPvvzgVwPSYNDo,1042
2
+ trilogy/__init__.py,sha256=KpC5q9qz6KQi0DSThY-iVF-bEm7ue6IBVE2nlKbQJHk,304
3
3
  trilogy/constants.py,sha256=g_zkVCNjGop6coZ1kM8eXXAzCnUN22ldx3TYFz0E9sc,1747
4
- trilogy/engine.py,sha256=3MiADf5MKcmxqiHBuRqiYdsXiLj7oitDfVvXvHrfjkA,2178
5
- trilogy/executor.py,sha256=-VeOV0bTGmchHRHpRwFJDyl8FElUxDpwUTUix7hhIFM,17429
4
+ trilogy/engine.py,sha256=v4TpNktM4zZ9OX7jZH2nde4dpX5uAH2U23ELfULTCSg,2280
5
+ trilogy/executor.py,sha256=q3EsAjzgxNxPn-yTHD_FTFzm7bJ2mlf9CrJEjyt6-pE,17884
6
6
  trilogy/parser.py,sha256=o4cfk3j3yhUFoiDKq9ZX_GjBF3dKhDjXEwb63rcBkBM,293
7
7
  trilogy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  trilogy/render.py,sha256=qQWwduymauOlB517UtM-VGbVe8Cswa4UJub5aGbSO6c,1512
@@ -32,7 +32,7 @@ trilogy/core/functions.py,sha256=sdV6Z3NUVfwL1d18eNcaAXllVNqzLez23McsJ6xIp7M,331
32
32
  trilogy/core/graph_models.py,sha256=4EWFTHGfYd72zvS2HYoV6hm7nMC_VEd7vWr6txY-ig0,3400
33
33
  trilogy/core/internal.py,sha256=r9QagDB2GvpqlyD_I7VrsfbVfIk5mnok2znEbv72Aa4,2681
34
34
  trilogy/core/optimization.py,sha256=Km0ITEx9n6Iv5ReX6tm4uXO5uniSv_ooahycNNiET3g,9212
35
- trilogy/core/query_processor.py,sha256=uqygDJqkjIH4vLP-lbGRgTN7rRcYEkr3KGqNimNw_80,20345
35
+ trilogy/core/query_processor.py,sha256=rMrtLSQxVm7yeyh0nWjDNI9nnu4Xi0NgHvBJ14gvu4I,20384
36
36
  trilogy/core/utility.py,sha256=3VC13uSQWcZNghgt7Ot0ZTeEmNqs__cx122abVq9qhM,410
37
37
  trilogy/core/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
38
38
  trilogy/core/models/author.py,sha256=3I7PFpJgoQT9RPOT3DfiqAjEtkcQPJnScs60I2UoyWo,81461
@@ -41,7 +41,7 @@ trilogy/core/models/build_environment.py,sha256=mpx7MKGc60fnZLVdeLi2YSREy7eQbQYy
41
41
  trilogy/core/models/core.py,sha256=iT9WdZoiXeglmUHWn6bZyXCTBpkApTGPKtNm_Mhbu_g,12987
42
42
  trilogy/core/models/datasource.py,sha256=wogTevZ-9CyUW2a8gjzqMCieircxi-J5lkI7EOAZnck,9596
43
43
  trilogy/core/models/environment.py,sha256=hwTIRnJgaHUdCYof7U5A9NPitGZ2s9yxqiW5O2SaJ9Y,28759
44
- trilogy/core/models/execute.py,sha256=pdL3voYB4dCQR_KMHwFaofP3ZpRbALRC2ELHueWyTko,42191
44
+ trilogy/core/models/execute.py,sha256=3fgEdho2e7S0outq91cCzb9jFwz6L1hTbsTrJwGvIFs,42311
45
45
  trilogy/core/optimizations/__init__.py,sha256=yspWc25M5SgAuvXYoSt5J8atyPbDlOfsKjIo5yGD9s4,368
46
46
  trilogy/core/optimizations/base_optimization.py,sha256=gzDOKImoFn36k7XBD3ysEYDnbnb6vdVIztUfFQZsGnM,513
47
47
  trilogy/core/optimizations/hide_unused_concept.py,sha256=DbsP8NqQOxmPv9omDOoFNPUGObUkqsRRNrr5d1xDxx4,1962
@@ -50,18 +50,18 @@ trilogy/core/optimizations/predicate_pushdown.py,sha256=5ubatgq1IwWQ4L2FDt4--y16
50
50
  trilogy/core/processing/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
51
51
  trilogy/core/processing/concept_strategies_v3.py,sha256=-iC2CLALmSrOglMKZM4TslVncyOrJbUH0V_COmbqHIw,22681
52
52
  trilogy/core/processing/discovery_node_factory.py,sha256=p23jiiHyhrW-Q8ndbnRlqMHJKT8ZqPOA89SzE4xaFFo,15445
53
- trilogy/core/processing/discovery_utility.py,sha256=KTWArF3zK2P2UfZxY1Y_L0-4SCx0UZlfp-VcjpSkhMI,13191
53
+ trilogy/core/processing/discovery_utility.py,sha256=BfzeliD0CQ3x3MRLpw3msibGc7rOPh8aH_gbfuchAIs,13352
54
54
  trilogy/core/processing/discovery_validation.py,sha256=eZ4HfHMpqZLI8MGG2jez8arS8THs6ceuVrQFIY6gXrU,5364
55
55
  trilogy/core/processing/graph_utils.py,sha256=8QUVrkE9j-9C1AyrCb1nQEh8daCe0u1HuXl-Te85lag,1205
56
56
  trilogy/core/processing/utility.py,sha256=ESs6pKqVP2c9eMdfB2JNjw7D7YnoezVwbLFx1D6OUYA,26088
57
57
  trilogy/core/processing/node_generators/__init__.py,sha256=iVJ-crowPxYeut-hFjyEjfibKIDq7PfB4LEuDAUCjGY,943
58
- trilogy/core/processing/node_generators/basic_node.py,sha256=74LoVZXLinRvSzk2LmI1kwza96TnuH3ELoYRIbHB29A,5578
58
+ trilogy/core/processing/node_generators/basic_node.py,sha256=rv53il-W633by0plvbN5qaqznJfl60yPinLZGBAC_MI,5707
59
59
  trilogy/core/processing/node_generators/common.py,sha256=xF32Kf6B08dZgKs2SOow1HomptSiSC057GCUCHFlS5s,9464
60
60
  trilogy/core/processing/node_generators/constant_node.py,sha256=LfpDq2WrBRZ3tGsLxw77LuigKfhbteWWh9L8BGdMGwk,1146
61
61
  trilogy/core/processing/node_generators/filter_node.py,sha256=cJ5od1fAfvalaUDO2O4Y6Yrr2RukOCqey7f3zrKSBbI,10808
62
62
  trilogy/core/processing/node_generators/group_node.py,sha256=sIm1QYrF4EY6sk56A48B6MieCZqvaJLSQebih_aiKnQ,8567
63
63
  trilogy/core/processing/node_generators/group_to_node.py,sha256=jKcNCDOY6fNblrdZwaRU0sbUSr9H0moQbAxrGgX6iGA,3832
64
- trilogy/core/processing/node_generators/multiselect_node.py,sha256=a505AEixjsjp5jI8Ng3H5KF_AaehkS6HfRfTef64l_o,7063
64
+ trilogy/core/processing/node_generators/multiselect_node.py,sha256=dHPDoSKU0FF6Ue_t_LkZxTd0Q-Sf-EpYdsMYdyUlFQc,7120
65
65
  trilogy/core/processing/node_generators/node_merge_node.py,sha256=hNcZxnDLTZyYJWfojg769zH9HB9PfZfESmpN1lcHWXg,23172
66
66
  trilogy/core/processing/node_generators/recursive_node.py,sha256=l5zdh0dURKwmAy8kK4OpMtZfyUEQRk6N-PwSWIyBpSM,2468
67
67
  trilogy/core/processing/node_generators/rowset_node.py,sha256=MuVNIexXhqGONho_mewqMOwaYXNUnjjvyPvk_RDGNYE,5943
@@ -74,10 +74,10 @@ trilogy/core/processing/node_generators/window_node.py,sha256=wNvmumGO6AIQ7C9bDU
74
74
  trilogy/core/processing/node_generators/select_helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
75
  trilogy/core/processing/node_generators/select_helpers/datasource_injection.py,sha256=m2YQ4OmG0N2O61a7NEq1ZzbTa7JsCC00lxB2ymjcYRI,8224
76
76
  trilogy/core/processing/nodes/__init__.py,sha256=zTge1EzwzEydlcMliIFO_TT7h7lS8l37lyZuQDir1h0,5487
77
- trilogy/core/processing/nodes/base_node.py,sha256=6LPQ5zP_dZJ6-k_dmX9ZSLsHaQMHgqiR5DEylpHYGZA,18478
77
+ trilogy/core/processing/nodes/base_node.py,sha256=k4Z9qdL9BXxxHH66L5udQwSDcYeiaWEn3bRmy84SShs,18559
78
78
  trilogy/core/processing/nodes/filter_node.py,sha256=5VtRfKbCORx0dV-vQfgy3gOEkmmscL9f31ExvlODwvY,2461
79
79
  trilogy/core/processing/nodes/group_node.py,sha256=Ku8El9KQvRiTiHCZDS_jX0DjErSDNv7IIQMcd1Gsk7I,7449
80
- trilogy/core/processing/nodes/merge_node.py,sha256=uc0tlz30Yt9SnCwLhMcWuPVbXLzm3dzy0XqbyirqqTo,16521
80
+ trilogy/core/processing/nodes/merge_node.py,sha256=4y_itKoipHKjpCIQjK9SHga-Fq-HqyeQLwAoSIFQ1hM,16567
81
81
  trilogy/core/processing/nodes/recursive_node.py,sha256=k0rizxR8KE64ievfHx_GPfQmU8QAP118Laeyq5BLUOk,1526
82
82
  trilogy/core/processing/nodes/select_node_v2.py,sha256=IWyKyNgFlV8A2S1FUTPdIaogg6PzaHh-HmQo6v24sbg,8862
83
83
  trilogy/core/processing/nodes/union_node.py,sha256=hLAXXVWqEgMWi7dlgSHfCF59fon64av14-uPgJzoKzM,1870
@@ -97,9 +97,9 @@ trilogy/core/validation/fix.py,sha256=Z818UFNLxndMTLiyhB3doLxIfnOZ-16QGvVFWuD7Us
97
97
  trilogy/dialect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
98
98
  trilogy/dialect/base.py,sha256=Qk4HkjKlnAnhcZwwLte9Arb_1pVnBmkgRlwRFX1A_GQ,50680
99
99
  trilogy/dialect/bigquery.py,sha256=XS3hpybeowgfrOrkycAigAF3NX2YUzTzfgE6f__2fT4,4316
100
- trilogy/dialect/common.py,sha256=n9sQeXQ0bOXHDN_jQRuxr1rG7xPBRwaizvSz16MjIDY,5663
100
+ trilogy/dialect/common.py,sha256=I5Ku_TR5MwJTB3ZhcQenrtvXhH2RvTQ8wQe9w5lfkfA,5708
101
101
  trilogy/dialect/config.py,sha256=olnyeVU5W5T6b9-dMeNAnvxuPlyc2uefb7FRME094Ec,3834
102
- trilogy/dialect/dataframe.py,sha256=RUbNgReEa9g3pL6H7fP9lPTrAij5pkqedpZ99D8_5AE,1522
102
+ trilogy/dialect/dataframe.py,sha256=nDTHMSd7GiGjEhjAobrZND0w4zjr-vgOalM2Cdxjets,1596
103
103
  trilogy/dialect/duckdb.py,sha256=cRPyqnuMgjhZVaW9BYA360p-5OXle_1Xt65Yy0Vzbr4,5901
104
104
  trilogy/dialect/enums.py,sha256=FRNYQ5-w-B6-X0yXKNU5g9GowsMlERFogTC5u2nxL_s,4740
105
105
  trilogy/dialect/metadata.py,sha256=p_V-MYPQ2iR6fcTjagnptCIWtsZe4fTfoS_iXpavPzY,7098
@@ -132,8 +132,8 @@ trilogy/std/money.preql,sha256=XWwvAV3WxBsHX9zfptoYRnBigcfYwrYtBHXTME0xJuQ,2082
132
132
  trilogy/std/net.preql,sha256=WZCuvH87_rZntZiuGJMmBDMVKkdhTtxeHOkrXNwJ1EE,416
133
133
  trilogy/std/ranking.preql,sha256=LDoZrYyz4g3xsII9XwXfmstZD-_92i1Eox1UqkBIfi8,83
134
134
  trilogy/std/report.preql,sha256=LbV-XlHdfw0jgnQ8pV7acG95xrd1-p65fVpiIc-S7W4,202
135
- pytrilogy-0.0.3.110.dist-info/METADATA,sha256=WPrZFC0_2AmSkZqYb0Aefl8XPAlNXyQ3dohVqIGDicU,13289
136
- pytrilogy-0.0.3.110.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
137
- pytrilogy-0.0.3.110.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
138
- pytrilogy-0.0.3.110.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
139
- pytrilogy-0.0.3.110.dist-info/RECORD,,
135
+ pytrilogy-0.0.3.112.dist-info/METADATA,sha256=vUL4xdKsovl8VHq7P_NyBTDiI31R0E2EkcJYOog7A8Q,13289
136
+ pytrilogy-0.0.3.112.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
137
+ pytrilogy-0.0.3.112.dist-info/entry_points.txt,sha256=ewBPU2vLnVexZVnB-NrVj-p3E-4vukg83Zk8A55Wp2w,56
138
+ pytrilogy-0.0.3.112.dist-info/top_level.txt,sha256=cAy__NW_eMAa_yT9UnUNlZLFfxcg6eimUAZ184cdNiE,8
139
+ pytrilogy-0.0.3.112.dist-info/RECORD,,
trilogy/__init__.py CHANGED
@@ -4,6 +4,6 @@ from trilogy.dialect.enums import Dialects
4
4
  from trilogy.executor import Executor
5
5
  from trilogy.parser import parse
6
6
 
7
- __version__ = "0.0.3.110"
7
+ __version__ = "0.0.3.112"
8
8
 
9
9
  __all__ = ["parse", "Executor", "Dialects", "Environment", "CONFIG"]
@@ -500,6 +500,7 @@ class BaseJoin(BaseModel):
500
500
  concepts: Optional[List[BuildConcept]] = None
501
501
  left_datasource: Optional[Union[BuildDatasource, "QueryDatasource"]] = None
502
502
  concept_pairs: list[ConceptPair] | None = None
503
+ modifiers: List[Modifier] = Field(default_factory=list)
503
504
 
504
505
  @model_validator(mode="after")
505
506
  def validate_join(self) -> "BaseJoin":
@@ -1103,6 +1104,7 @@ class Join(BaseModel):
1103
1104
  inlined_ctes: set[str] = Field(default_factory=set)
1104
1105
  quote: str | None = None
1105
1106
  condition: BuildConditional | BuildComparison | BuildParenthetical | None = None
1107
+ modifiers: List[Modifier] = Field(default_factory=list)
1106
1108
 
1107
1109
  def inline_cte(self, cte: CTE):
1108
1110
  self.inlined_ctes.add(cte.name)
@@ -1,7 +1,7 @@
1
1
  from typing import List
2
2
 
3
3
  from trilogy.constants import logger
4
- from trilogy.core.enums import Derivation, Purpose
4
+ from trilogy.core.enums import Derivation, Purpose, SourceType
5
5
  from trilogy.core.models.build import (
6
6
  BuildConcept,
7
7
  BuildDatasource,
@@ -200,8 +200,10 @@ def group_if_required_v2(
200
200
  root.set_output_concepts(targets, rebuild=False, change_visibility=False)
201
201
  root.rebuild_cache()
202
202
  return root
203
+ elif isinstance(root, GroupNode) and root.source_type == SourceType.BASIC:
204
+ # we need to group this one more time
205
+ pass
203
206
  elif isinstance(root, GroupNode):
204
-
205
207
  if set(x.address for x in final) != set(
206
208
  x.address for x in root.output_concepts
207
209
  ):
@@ -138,7 +138,9 @@ def gen_basic_node(
138
138
  for s in parent_node.output_concepts
139
139
  if any(s.address in y.pseudonyms for y in targets)
140
140
  ] + targets
141
- parent_node.set_output_concepts(targets)
141
+ hidden = [x for x in parent_node.output_concepts if x.address not in targets]
142
+ parent_node.hide_output_concepts(hidden)
143
+ parent_node.source_type = SourceType.BASIC
142
144
 
143
145
  logger.info(
144
146
  f"{depth_prefix}{LOGGER_PREFIX} Returning basic select for {concept}: input: {[x.address for x in parent_node.input_concepts]} output {[x.address for x in parent_node.output_concepts]} hidden {[x for x in parent_node.hidden_concepts]}"
@@ -3,7 +3,7 @@ from itertools import combinations
3
3
  from typing import List
4
4
 
5
5
  from trilogy.constants import logger
6
- from trilogy.core.enums import JoinType, Purpose
6
+ from trilogy.core.enums import JoinType, Modifier, Purpose
7
7
  from trilogy.core.models.build import (
8
8
  BuildConcept,
9
9
  BuildGrain,
@@ -47,6 +47,7 @@ def extra_align_joins(
47
47
  right_node=right,
48
48
  concepts=matched_concepts,
49
49
  join_type=JoinType.FULL,
50
+ modifiers=[Modifier.NULLABLE],
50
51
  )
51
52
  )
52
53
  return resolve_join_order(output)
@@ -1,11 +1,12 @@
1
1
  from collections import defaultdict
2
- from dataclasses import dataclass
2
+ from dataclasses import dataclass, field
3
3
  from typing import List, Optional
4
4
 
5
5
  from trilogy.core.enums import (
6
6
  BooleanOperator,
7
7
  Derivation,
8
8
  JoinType,
9
+ Modifier,
9
10
  SourceType,
10
11
  )
11
12
  from trilogy.core.models.build import (
@@ -436,6 +437,7 @@ class NodeJoin:
436
437
  join_type: JoinType
437
438
  filter_to_mutual: bool = False
438
439
  concept_pairs: list[ConceptPair] | None = None
440
+ modifiers: List[Modifier] = field(default_factory=list)
439
441
 
440
442
  def __post_init__(self):
441
443
  if self.left_node == self.right_node:
@@ -169,6 +169,7 @@ class MergeNode(StrategyNode):
169
169
  join_type=join.join_type,
170
170
  concepts=join.concepts,
171
171
  concept_pairs=join.concept_pairs,
172
+ modifiers=join.modifiers,
172
173
  )
173
174
  )
174
175
  return joins
@@ -122,6 +122,7 @@ def base_join_to_join(
122
122
  right_cte=right_cte,
123
123
  jointype=base_join.join_type,
124
124
  joinkey_pairs=final_pairs,
125
+ modifiers=base_join.modifiers,
125
126
  )
126
127
 
127
128
 
trilogy/dialect/common.py CHANGED
@@ -131,7 +131,8 @@ def render_join(
131
131
  ),
132
132
  pair.modifiers
133
133
  + (pair.left.modifiers or [])
134
- + (pair.right.modifiers or []),
134
+ + (pair.right.modifiers or [])
135
+ + (join.modifiers or []),
135
136
  )
136
137
  for pair in join.joinkey_pairs
137
138
  ]
@@ -45,3 +45,6 @@ class DataframeConnectionWrapper(ExecutionEngine):
45
45
 
46
46
  def connect(self) -> Any:
47
47
  return self.engine.connect()
48
+
49
+ def dispose(self, close=True):
50
+ return super().dispose(close)
trilogy/engine.py CHANGED
@@ -33,6 +33,9 @@ class EngineConnection(Protocol):
33
33
  def rollback(self):
34
34
  raise NotImplementedError()
35
35
 
36
+ def close(self) -> None:
37
+ return
38
+
36
39
 
37
40
  class ExecutionEngine(Protocol):
38
41
  pass
@@ -43,6 +46,9 @@ class ExecutionEngine(Protocol):
43
46
  def setup(self, env: Environment, connection):
44
47
  pass
45
48
 
49
+ def dispose(self, close: bool = True):
50
+ pass
51
+
46
52
 
47
53
  ### Begin default SQLAlchemy implementation
48
54
  class SqlAlchemyResult:
trilogy/executor.py CHANGED
@@ -47,7 +47,7 @@ from trilogy.dialect.metadata import (
47
47
  handle_processed_validate_statement,
48
48
  handle_show_statement_outputs,
49
49
  )
50
- from trilogy.engine import ExecutionEngine, ResultProtocol
50
+ from trilogy.engine import EngineConnection, ExecutionEngine, ResultProtocol
51
51
  from trilogy.hooks.base_hook import BaseHook
52
52
  from trilogy.parser import parse_text
53
53
  from trilogy.render import get_dialect_generator
@@ -69,11 +69,24 @@ class Executor(object):
69
69
  self.logger = logger
70
70
  self.hooks = hooks
71
71
  self.generator = get_dialect_generator(self.dialect, rendering)
72
- self.connection = self.engine.connect()
72
+ self.connection = self.connect()
73
73
  # TODO: make generic
74
74
  if self.dialect == Dialects.DATAFRAME:
75
75
  self.engine.setup(self.environment, self.connection)
76
76
 
77
+ def connect(self) -> EngineConnection:
78
+ self.connection = self.engine.connect()
79
+ self.connected = True
80
+ return self.connection
81
+
82
+ def close(self):
83
+ self.engine.dispose(close=True)
84
+ if self.dialect == Dialects.DUCK_DB:
85
+ import gc
86
+
87
+ gc.collect()
88
+ self.connected = False
89
+
77
90
  def execute_statement(
78
91
  self,
79
92
  statement: PROCESSED_STATEMENT_TYPES,
@@ -244,7 +257,9 @@ class Executor(object):
244
257
  """generate SQL for execution"""
245
258
  _, parsed = parse_text(command, self.environment)
246
259
  generatable = [
247
- x for x in parsed if isinstance(x, (SelectStatement, PersistStatement))
260
+ x
261
+ for x in parsed
262
+ if isinstance(x, (SelectStatement, PersistStatement, MultiSelectStatement))
248
263
  ]
249
264
  sql = self.generator.generate_queries(
250
265
  self.environment, generatable, hooks=self.hooks
@@ -419,6 +434,9 @@ class Executor(object):
419
434
  def execute_text(
420
435
  self, command: str, non_interactive: bool = False
421
436
  ) -> List[ResultProtocol]:
437
+ if not self.connected:
438
+ self.connect()
439
+
422
440
  """Run a trilogy query expressed as text."""
423
441
  output: list[ResultProtocol] = []
424
442
  # connection = self.engine.connect()