14
14
from six .moves import zip
15
15
from tabulator import config as tabulator_config , EncodingError , Stream , TabulatorException
16
16
from unidecode import unidecode
17
+ import sqlalchemy as sa
17
18
18
19
import ckan .plugins as p
19
20
@@ -118,8 +119,8 @@ def _clear_datastore_resource(resource_id):
118
119
'''
119
120
engine = get_write_engine ()
120
121
with engine .begin () as conn :
121
- conn .execute ("SET LOCAL lock_timeout = '15s'" )
122
- conn .execute ('TRUNCATE TABLE "{}" RESTART IDENTITY' .format (resource_id ))
122
+ conn .execute (sa . text ( "SET LOCAL lock_timeout = '15s'" ) )
123
+ conn .execute (sa . text ( 'TRUNCATE TABLE "{}" RESTART IDENTITY' .format (resource_id ) ))
123
124
124
125
125
126
def load_csv (csv_filepath , resource_id , mimetype = 'text/csv' , logger = None ):
@@ -282,12 +283,17 @@ def strip_white_space_iter():
282
283
except Exception as e :
283
284
raise LoaderError ('Could not create the database table: {}'
284
285
.format (e ))
285
- connection = context [ 'connection' ] = engine . connect ()
286
+
286
287
287
288
# datstore_active is switched on by datastore_create - TODO temporarily
288
289
# disable it until the load is complete
289
- _disable_fulltext_trigger (connection , resource_id )
290
- _drop_indexes (context , data_dict , False )
290
+
291
+ with engine .begin () as conn :
292
+ _disable_fulltext_trigger (conn , resource_id )
293
+
294
+ with engine .begin () as conn :
295
+ context ['connection' ] = conn
296
+ _drop_indexes (context , data_dict , False )
291
297
292
298
logger .info ('Copying to database...' )
293
299
@@ -305,9 +311,8 @@ def strip_white_space_iter():
305
311
# 4. COPY FROM STDIN - not quite as fast as COPY from a file, but avoids
306
312
# the superuser issue. <-- picked
307
313
308
- raw_connection = engine .raw_connection ()
309
- try :
310
- cur = raw_connection .cursor ()
314
+ with engine .begin () as conn :
315
+ cur = conn .connection .cursor ()
311
316
try :
312
317
with open (csv_filepath , 'rb' ) as f :
313
318
# can't use :param for table name because params are only
@@ -337,15 +342,14 @@ def strip_white_space_iter():
337
342
338
343
finally :
339
344
cur .close ()
340
- finally :
341
- raw_connection .commit ()
342
345
finally :
343
346
os .remove (csv_filepath ) # i.e. the tempfile
344
347
345
348
logger .info ('...copying done' )
346
349
347
350
logger .info ('Creating search index...' )
348
- _populate_fulltext (connection , resource_id , fields = fields )
351
+ with engine .begin () as conn :
352
+ _populate_fulltext (conn , resource_id , fields = fields )
349
353
logger .info ('...search index created' )
350
354
351
355
return fields
@@ -631,9 +635,9 @@ def fulltext_function_exists(connection):
631
635
https://github.com/ckan/ckan/pull/3786
632
636
or otherwise it is checked on startup of this plugin.
633
637
'''
634
- res = connection .execute ('''
638
+ res = connection .execute (sa . text ( '''
635
639
select * from pg_proc where proname = 'populate_full_text_trigger';
636
- ''' )
640
+ ''' ))
637
641
return bool (res .rowcount )
638
642
639
643
@@ -642,24 +646,25 @@ def fulltext_trigger_exists(connection, resource_id):
642
646
This will only be the case if your CKAN is new enough to have:
643
647
https://github.com/ckan/ckan/pull/3786
644
648
'''
645
- res = connection .execute ('''
649
+ res = connection .execute (sa . text ( '''
646
650
SELECT pg_trigger.tgname FROM pg_class
647
651
JOIN pg_trigger ON pg_class.oid=pg_trigger.tgrelid
648
652
WHERE pg_class.relname={table}
649
653
AND pg_trigger.tgname='zfulltext';
650
654
''' .format (
651
- table = literal_string (resource_id )))
655
+ table = literal_string (resource_id ))))
652
656
return bool (res .rowcount )
653
657
654
658
655
659
def _disable_fulltext_trigger (connection , resource_id ):
656
- connection .execute ('ALTER TABLE {table} DISABLE TRIGGER zfulltext;'
657
- .format (table = identifier (resource_id )))
660
+ connection .execute (sa . text ( 'ALTER TABLE {table} DISABLE TRIGGER zfulltext;'
661
+ .format (table = identifier (resource_id , True ) )))
658
662
659
663
660
664
def _enable_fulltext_trigger (connection , resource_id ):
661
- connection .execute ('ALTER TABLE {table} ENABLE TRIGGER zfulltext;'
662
- .format (table = identifier (resource_id )))
665
+ connection .execute (sa .text (
666
+ 'ALTER TABLE {table} ENABLE TRIGGER zfulltext;'
667
+ .format (table = identifier (resource_id , True ))))
663
668
664
669
665
670
def _populate_fulltext (connection , resource_id , fields ):
@@ -672,23 +677,20 @@ def _populate_fulltext(connection, resource_id, fields):
672
677
fields: list of dicts giving the each column's 'id' (name) and 'type'
673
678
(text/numeric/timestamp)
674
679
'''
675
- sql = \
676
- u'''
677
- UPDATE {table}
678
- SET _full_text = to_tsvector({cols});
679
- ''' .format (
680
- # coalesce copes with blank cells
681
- table = identifier (resource_id ),
682
- cols = " || ' ' || " .join (
680
+ stmt = sa .update (sa .table (resource_id , sa .column ("_full_text" ))).values (
681
+ _full_text = sa .text ("to_tsvector({})" .format (
682
+ " || ' ' || " .join (
683
683
'coalesce({}, \' \' )' .format (
684
684
identifier (field ['id' ])
685
685
+ ('::text' if field ['type' ] != 'text' else '' )
686
686
)
687
687
for field in fields
688
688
if not field ['id' ].startswith ('_' )
689
689
)
690
- )
691
- connection .execute (sql )
690
+ ))
691
+ )
692
+
693
+ connection .execute (stmt )
692
694
693
695
694
696
def calculate_record_count (resource_id , logger ):
@@ -700,15 +702,18 @@ def calculate_record_count(resource_id, logger):
700
702
logger .info ('Calculating record count (running ANALYZE on the table)' )
701
703
engine = get_write_engine ()
702
704
conn = engine .connect ()
703
- conn .execute ("ANALYZE \" {resource_id}\" ;"
704
- .format (resource_id = resource_id ))
705
+ conn .execute (sa . text ( "ANALYZE \" {resource_id}\" ;"
706
+ .format (resource_id = resource_id ) ))
705
707
706
708
707
- def identifier (s ):
709
+ def identifier (s , escape_binds = False ):
708
710
# "%" needs to be escaped, otherwise connection.execute thinks it is for
709
711
# substituting a bind parameter
710
- return u'"' + s .replace (u'"' , u'""' ).replace (u'\0 ' , '' ).replace ('%' , '%%' )\
711
- + u'"'
712
+ escaped = s .replace (u'"' , u'""' ).replace (u'\0 ' , '' )
713
+ if escape_binds :
714
+ escaped = escaped .replace ('%' , '%%' )
715
+
716
+ return u'"' + escaped + u'"'
712
717
713
718
714
719
def literal_string (s ):
0 commit comments