@@ -134,34 +134,58 @@ def _register_in_memory_table(self, op: ops.InMemoryTable) -> None:
134
134
135
135
con = self .con
136
136
with con .cursor () as cursor , con .transaction ():
137
- cursor .execute (create_stmt_sql )
138
- cursor .executemany (sql , data )
137
+ cursor .execute (create_stmt_sql ).executemany (sql , data )
139
138
140
139
@contextlib .contextmanager
141
140
def begin (self ):
142
141
with (con := self .con ).cursor () as cursor , con .transaction ():
143
142
yield cursor
144
143
145
- def _fetch_from_cursor (
146
- self , cursor : psycopg .Cursor , schema : sch .Schema
147
- ) -> pd .DataFrame :
144
+ def execute (
145
+ self ,
146
+ expr : ir .Expr ,
147
+ / ,
148
+ * ,
149
+ params : Mapping [ir .Scalar , Any ] | None = None ,
150
+ limit : int | str | None = None ,
151
+ ** kwargs : Any ,
152
+ ) -> pd .DataFrame | pd .Series | Any :
153
+ """Execute an Ibis expression and return a pandas `DataFrame`, `Series`, or scalar.
154
+
155
+ Parameters
156
+ ----------
157
+ expr
158
+ Ibis expression to execute.
159
+ params
160
+ Mapping of scalar parameter expressions to value.
161
+ limit
162
+ An integer to effect a specific row limit. A value of `None` means
163
+ no limit. The default is in `ibis/config.py`.
164
+ kwargs
165
+ Keyword arguments
166
+
167
+ Returns
168
+ -------
169
+ DataFrame | Series | scalar
170
+ The result of the expression execution.
171
+ """
148
172
import pandas as pd
149
173
150
174
from ibis .backends .postgres .converter import PostgresPandasData
151
175
152
- try :
153
- df = pd . DataFrame . from_records (
154
- cursor . fetchall (), columns = schema . names , coerce_float = True
155
- )
156
- except Exception :
157
- # clean up the cursor if we fail to create the DataFrame
158
- #
159
- # in the sqlite case failing to close the cursor results in
160
- # artificially locked tables
161
- cursor . close ()
162
- raise
176
+ self . _run_pre_execute_hooks ( expr )
177
+
178
+ table = expr . as_table ()
179
+ sql = self . compile ( table , params = params , limit = limit , ** kwargs )
180
+
181
+ con = self . con
182
+ with con . cursor () as cur , con . transaction ():
183
+ rows = cur . execute ( sql ). fetchall ()
184
+
185
+ schema = table . schema ()
186
+ df = pd . DataFrame . from_records ( rows , columns = schema . names , coerce_float = True )
163
187
df = PostgresPandasData .convert_table (df , schema )
164
- return df
188
+ return expr . __pandas_result__ ( df )
165
189
166
190
@property
167
191
def version (self ):
@@ -352,43 +376,34 @@ def list_tables(
352
376
catalog = catalog .sql (dialect = self .name )
353
377
conditions .append (C .table_catalog .eq (sge .convert (catalog )))
354
378
355
- sql = (
356
- sg .select (" table_name" )
379
+ sg_expr = (
380
+ sg .select (C . table_name )
357
381
.from_ (sg .table ("tables" , db = "information_schema" ))
358
382
.distinct ()
359
383
.where (* conditions )
360
- .sql (self .dialect )
361
384
)
362
385
363
- con = self .con
364
- with con .cursor () as cursor , con .transaction ():
365
- out = cursor .execute (sql ).fetchall ()
366
-
367
- # Include temporary tables only if no database has been explicitly specified
368
- # to avoid temp tables showing up in all calls to `list_tables`
386
+ # Include temporary tables only if no database has been explicitly
387
+ # specified to avoid temp tables showing up in all calls to
388
+ # `list_tables`
369
389
if db == "public" :
370
- out += self ._fetch_temp_tables ()
371
-
372
- return self ._filter_with_like (map (itemgetter (0 ), out ), like )
373
-
374
- def _fetch_temp_tables (self ):
375
- # postgres temporary tables are stored in a separate schema
376
- # so we need to independently grab them and return them along with
377
- # the existing results
378
-
379
- sql = (
380
- sg .select ("table_name" )
381
- .from_ (sg .table ("tables" , db = "information_schema" ))
382
- .distinct ()
383
- .where (C .table_type .eq (sge .convert ("LOCAL TEMPORARY" )))
384
- .sql (self .dialect )
385
- )
390
+ # postgres temporary tables are stored in a separate schema so we need
391
+ # to independently grab them and return them along with the existing
392
+ # results
393
+ sg_expr = sg_expr .union (
394
+ sg .select (C .table_name )
395
+ .from_ (sg .table ("tables" , db = "information_schema" ))
396
+ .distinct ()
397
+ .where (C .table_type .eq (sge .convert ("LOCAL TEMPORARY" ))),
398
+ distinct = False ,
399
+ )
386
400
401
+ sql = sg_expr .sql (self .dialect )
387
402
con = self .con
388
403
with con .cursor () as cursor , con .transaction ():
389
404
out = cursor .execute (sql ).fetchall ()
390
405
391
- return out
406
+ return self . _filter_with_like ( map ( itemgetter ( 0 ), out ), like )
392
407
393
408
def list_catalogs (self , * , like : str | None = None ) -> list [str ]:
394
409
# http://dba.stackexchange.com/a/1304/58517
@@ -400,9 +415,9 @@ def list_catalogs(self, *, like: str | None = None) -> list[str]:
400
415
)
401
416
con = self .con
402
417
with con .cursor () as cursor , con .transaction ():
403
- catalogs = list ( map ( itemgetter ( 0 ), cursor .execute (cats )) )
418
+ catalogs = cursor .execute (cats ). fetchall ( )
404
419
405
- return self ._filter_with_like (catalogs , like )
420
+ return self ._filter_with_like (map ( itemgetter ( 0 ), catalogs ) , like )
406
421
407
422
def list_databases (
408
423
self , * , like : str | None = None , catalog : str | None = None
@@ -414,24 +429,24 @@ def list_databases(
414
429
)
415
430
con = self .con
416
431
with con .cursor () as cursor , con .transaction ():
417
- databases = list ( map ( itemgetter ( 0 ), cursor .execute (dbs )) )
432
+ databases = cursor .execute (dbs ). fetchall ( )
418
433
419
- return self ._filter_with_like (databases , like )
434
+ return self ._filter_with_like (map ( itemgetter ( 0 ), databases ) , like )
420
435
421
436
@property
422
437
def current_catalog (self ) -> str :
423
438
sql = sg .select (sg .func ("current_database" )).sql (self .dialect )
424
439
con = self .con
425
440
with con .cursor () as cursor , con .transaction ():
426
- (db ,) = cursor .execute (sql ).fetchone ()
441
+ [ (db ,)] = cursor .execute (sql ).fetchall ()
427
442
return db
428
443
429
444
@property
430
445
def current_database (self ) -> str :
431
446
sql = sg .select (sg .func ("current_schema" )).sql (self .dialect )
432
447
con = self .con
433
448
with con .cursor () as cursor , con .transaction ():
434
- (schema ,) = cursor .execute (sql ).fetchone ()
449
+ [ (schema ,)] = cursor .execute (sql ).fetchall ()
435
450
return schema
436
451
437
452
def function (self , name : str , * , database : str | None = None ) -> Callable :
@@ -698,20 +713,20 @@ def create_table(
698
713
this_no_catalog = sg .table (name , quoted = quoted )
699
714
700
715
con = self .con
701
- with con .cursor () as cursor , con .transaction ():
702
- cursor .execute (create_stmt )
716
+ stmts = [create_stmt ]
703
717
704
- if query is not None :
705
- insert_stmt = sge .Insert (this = table_expr , expression = query ).sql (dialect )
706
- cursor .execute (insert_stmt )
718
+ if query is not None :
719
+ stmts .append (sge .Insert (this = table_expr , expression = query ).sql (dialect ))
707
720
708
- if overwrite :
709
- cursor .execute (
710
- sge .Drop (kind = "TABLE" , this = this , exists = True ).sql (dialect )
711
- )
712
- cursor .execute (
713
- f"ALTER TABLE IF EXISTS { table_expr .sql (dialect )} RENAME TO { this_no_catalog .sql (dialect )} "
714
- )
721
+ if overwrite :
722
+ stmts .append (sge .Drop (kind = "TABLE" , this = this , exists = True ).sql (dialect ))
723
+ stmts .append (
724
+ f"ALTER TABLE IF EXISTS { table_expr .sql (dialect )} RENAME TO { this_no_catalog .sql (dialect )} "
725
+ )
726
+
727
+ with con .cursor () as cursor , con .transaction ():
728
+ for stmt in stmts :
729
+ cursor .execute (stmt )
715
730
716
731
if schema is None :
717
732
return self .table (name , database = database )
@@ -743,7 +758,8 @@ def _safe_raw_sql(self, query: str | sg.Expression, **kwargs: Any):
743
758
with contextlib .suppress (AttributeError ):
744
759
query = query .sql (dialect = self .dialect )
745
760
746
- with (con := self .con ).cursor () as cursor , con .transaction ():
761
+ con = self .con
762
+ with con .cursor () as cursor , con .transaction ():
747
763
yield cursor .execute (query , ** kwargs )
748
764
749
765
def raw_sql (self , query : str | sg .Expression , ** kwargs : Any ) -> Any :
@@ -757,6 +773,7 @@ def raw_sql(self, query: str | sg.Expression, **kwargs: Any) -> Any:
757
773
cursor .execute (query , ** kwargs )
758
774
except Exception :
759
775
cursor .close ()
776
+ con .rollback ()
760
777
raise
761
778
else :
762
779
return cursor
@@ -783,8 +800,8 @@ def _batches(self: Self, *, schema: pa.Schema, query: str):
783
800
con .cursor (name = util .gen_name ("postgres_cursor" )) as cursor ,
784
801
con .transaction (),
785
802
):
786
- cursor .execute (query )
787
- while batch := cursor .fetchmany (chunk_size ):
803
+ cur = cursor .execute (query )
804
+ while batch := cur .fetchmany (chunk_size ):
788
805
yield pa .RecordBatch .from_pandas (
789
806
pd .DataFrame (batch , columns = columns ), schema = schema
790
807
)
0 commit comments