Projet

Général

Profil

0001-quote-all-templated-field-and-table-names-36503.patch

Benjamin Dauvergne, 28 septembre 2019 11:02

Télécharger (17,9 ko)

Voir les différences:

Subject: [PATCH] quote all templated field and table names (#36503)

 tests/olap.model   |  12 ++---
 wcs_olap/feeder.py | 115 +++++++++++++++++++++++----------------------
 2 files changed, 66 insertions(+), 61 deletions(-)
tests/olap.model
158 158
               "type" : "percent"
159 159
            },
160 160
            {
161
               "expression" : "array_agg(\"{fact_table}\".geolocation_base) FILTER (WHERE \"{fact_table}\".geolocation_base IS NOT NULL)",
161
               "expression" : "array_agg({fact_table}.geolocation_base) FILTER (WHERE {fact_table}.geolocation_base IS NOT NULL)",
162 162
               "label" : "localisation géographique",
163 163
               "name" : "geolocation",
164 164
               "type" : "point"
......
284 284
               "value": "\"field_stringCaseSensitive\""
285 285
            }
286 286
         ],
287
         "fact_table" : "formdata_demande",
287
         "fact_table" : "\"formdata_demande\"",
288 288
         "joins" : [
289 289
            {
290 290
               "detail" : "date",
......
338 338
            },
339 339
            {
340 340
               "detail" : "id",
341
               "master" : "function__receiver",
341
               "master" : "\"function__receiver\"",
342 342
               "name" : "function__receiver",
343 343
               "table" : "role"
344 344
            },
......
346 346
               "detail" : "id",
347 347
               "master" : "\"field_item\"",
348 348
               "name" : "item",
349
               "table" : "formdata_demande_field_item"
349
               "table" : "\"formdata_demande_field_item\""
350 350
            },
351 351
            {
352 352
               "detail" : "id",
353 353
               "master" : "\"field_itemOpen\"",
354 354
               "name" : "itemOpen",
355
               "table" : "formdata_demande_field_itemOpen"
355
               "table" : "\"formdata_demande_field_itemOpen\""
356 356
            }
357 357
         ],
358 358
         "key" : "id",
......
389 389
               "type" : "percent"
390 390
            },
391 391
            {
392
               "expression" : "array_agg(\"{fact_table}\".geolocation_base) FILTER (WHERE \"{fact_table}\".geolocation_base IS NOT NULL)",
392
               "expression" : "array_agg({fact_table}.geolocation_base) FILTER (WHERE {fact_table}.geolocation_base IS NOT NULL)",
393 393
               "label" : "localisation géographique",
394 394
               "name" : "geolocation",
395 395
               "type" : "point"
wcs_olap/feeder.py
14 14
from wcs_olap.wcs_api import WcsApiError
15 15

  
16 16

  
17
def quote(name):
18
    return '"%s"' % name
19

  
20

  
17 21
def slugify(s):
18 22
    return s.replace('-', '_').replace(' ', '_')
19 23

  
......
250 254
                    'name': 'geolocation',
251 255
                    'label': 'localisation géographique',
252 256
                    'type': 'point',
253
                    'expression': 'array_agg("{fact_table}".geolocation_base) FILTER (WHERE "{fact_table}".geolocation_base IS NOT NULL)',
257
                    'expression': 'array_agg({fact_table}.geolocation_base) '
258
                                  'FILTER (WHERE {fact_table}.geolocation_base IS NOT NULL)',
254 259
                }
255 260
            ]
256 261
        }
......
344 349
    AS the_date(the_date));''' % last_date.strftime('%Y-%m-%d'))
345 350

  
346 351
    def create_table(self, name, columns, inherits=None, comment=None):
347
        sql = 'CREATE TABLE "%s"' % name
352
        sql = 'CREATE TABLE %s' % quote(name)
348 353
        sql += '(' + ', '.join('"%s" %s' % (n, t) for n, t in columns) + ')'
349 354
        if inherits:
350 355
            sql += ' INHERITS ("%s")' % inherits
......
365 370
        if self.prev_table_exists(name):
366 371
            # Insert data from previous table
367 372
            self.ex(
368
                'INSERT INTO {schema_temp}."{name}" SELECT * FROM {schema}."{name}"',
369
                ctx={'name': name}
373
                'INSERT INTO {schema_temp}.{name} SELECT * FROM {schema}.{name}',
374
                ctx={'name': quote(name)}
370 375
            )
371 376
            # Update sequence
372
            self.ex("""SELECT setval(pg_get_serial_sequence('"{name}"', 'id'),
373
            (SELECT GREATEST(1, MAX(id)) FROM "{name}"))""", ctx={'name': name})
377
            self.ex("""SELECT setval(pg_get_serial_sequence('{name}', 'id'),
378
            (SELECT GREATEST(1, MAX(id)) FROM {name}))""", ctx={'name': quote(name)})
374 379

  
375 380
    def create_labeled_table(self, name, labels, comment=None):
376 381
        self.create_table(
......
383 388
        if self.prev_table_exists(name):
384 389
            # Insert data from previous table
385 390
            self.ex(
386
                'INSERT INTO {schema_temp}."{name}" select * FROM {schema}."{name}"',
387
                ctx={'name': name}
391
                'INSERT INTO {schema_temp}.{name} select * FROM {schema}.{name}',
392
                ctx={'name': quote(name)}
388 393
            )
389 394
            # Find what is missing
390 395
            to_insert = []
391 396
            for _id, _label in labels:
392 397
                self.ex(
393
                    'SELECT * FROM "{name}" WHERE label = %s', ctx={'name': name}, vars=(_label,))
398
                    'SELECT * FROM {name} WHERE label = %s', ctx={'name': quote(name)}, vars=(_label,))
394 399
                if self.cur.fetchone() is None:
395 400
                    to_insert.append(_label)
396 401

  
397 402
            labels = None
398 403
            if to_insert:
399
                self.ex('SELECT MAX(id) FROM "{name}"', ctx={'name': name})
404
                self.ex('SELECT MAX(id) FROM {name}', ctx={'name': quote(name)})
400 405
                next_id = (self.cur.fetchone()[0] or 0) + 1
401 406
                ids = range(next_id, next_id + len(to_insert))
402 407
                labels = zip(ids, to_insert)
......
407 412

  
408 413
        if labels:
409 414
            tmpl = ', '.join(['(%s, %s)'] * len(labels))
410
            query_str = 'INSERT INTO "{name}" (id, label) VALUES %s' % tmpl
411
            self.ex(query_str, ctx={'name': name}, vars=list(itertools.chain(*labels)))
415
            query_str = 'INSERT INTO {name} (id, label) VALUES %s' % tmpl
416
            self.ex(query_str, ctx={'name': quote(name)}, vars=list(itertools.chain(*labels)))
412 417

  
413 418
        res = {}
414
        self.ex('SELECT id, label FROM "{name}"', ctx={'name': name})
419
        self.ex('SELECT id, label FROM {name}', ctx={'name': quote(name)})
415 420
        for id_, label in self.cur.fetchall():
416 421
            res[label] = id_
417 422
        return res
......
457 462

  
458 463
        self.create_labeled_table('status', self.status,
459 464
                                  comment=u'statuts simplifiés')
460
        self.ex('CREATE TABLE "{form_table}" (id serial PRIMARY KEY,'
465
        self.ex('CREATE TABLE {form_table} (id serial PRIMARY KEY,'
461 466
                ' category_id integer REFERENCES {category_table} (id),'
462 467
                ' label varchar)')
463
        self.ex('COMMENT ON TABLE "{form_table}" IS %s', vars=(u'types de formulaire',))
468
        self.ex('COMMENT ON TABLE {form_table} IS %s', vars=(u'types de formulaire',))
464 469
        # agents
465 470
        self.create_labeled_table_serial('agent', comment=u'agents')
466 471

  
......
552 557
            self.agents_mapping[user.id] = self.insert_agent(user.name)
553 558
        return self.agents_mapping[user.id]
554 559

  
555
    def create_formdata_json_index(self, varname):
560
    def create_formdata_json_index(self, table_name, varname):
556 561
        if varname in self.formdata_json_index:
557 562
            return
558
        index_name = self.hash_table_name('{formdata_table}_%s_json_idx' % varname,
559
                hash_length=8, force_hash=True)
563
        index_name = self.hash_table_name('%s_%s_json_idx' % (table_name, varname), hash_length=8,
564
                                          force_hash=True)
560 565
        self.ex('CREATE INDEX {index_name} ON {generic_formdata_table} (("json_data"->>%s))',
561
                ctx={'index_name': index_name}, vars=[varname])
566
                ctx={'index_name': quote(index_name)}, vars=[varname])
562 567
        # prevent double creation
563 568
        self.formdata_json_index.append(varname)
564 569

  
......
601 606
                      self.formdef.schema.name])
602 607
        self.formdef_sql_id = self.cur.fetchone()[0]
603 608

  
604
        columns = [['status_id', 'smallint REFERENCES "{status_table}" (id)']]
609
        columns = [['status_id', 'smallint REFERENCES {status_table} (id)']]
605 610

  
606 611
        comments = {}
607 612

  
......
623 628
            if field.type == 'item':
624 629
                comment = (u'valeurs du champ « %s » du formulaire %s'
625 630
                           % (field.label, self.formdef.schema.name))
626
                table_name = self.hash_table_name('{formdata_table}_field_%s' % field.varname)
631
                table_name = self.hash_table_name('%s_field_%s' % (self.table_name, field.varname))
627 632
                # create table and mapping
628 633
                if field.items:
629 634
                    self.items_mappings[field.varname] = self.create_labeled_table(
......
659 664
            name for name, _type in columns])
660 665
        self.columns.remove('geolocation_base')
661 666

  
662
        self.create_table('{formdata_table}', columns, inherits='{generic_formdata_table}',
667
        self.create_table(self.table_name, columns, inherits='{generic_formdata_table}',
663 668
                          comment=u'formulaire %s' % self.formdef.schema.name)
664 669
        for at, comment in comments.iteritems():
665
            self.ex('COMMENT ON COLUMN "{formdata_table}"."%s" IS %%s' % at, vars=(comment,))
670
            self.ex('COMMENT ON COLUMN {formdata_table}.%s IS %%s' % quote(at), vars=(comment,))
666 671

  
667 672
        # Creat index for JSON fields
668 673
        if self.has_jsonb:
669 674
            for field in fields:
670 675
                if field.varname and '-' not in field.varname:
671
                    self.create_formdata_json_index(field.varname)
676
                    self.create_formdata_json_index(self.table_name, field.varname)
672 677

  
673 678
        # PostgreSQL does not propagate foreign key constraints to child tables
674 679
        # so we must recreate them manually
......
677 682
                continue
678 683
            i = _type.index('REFERENCES')
679 684
            constraint = '%s_fk_constraint FOREIGN KEY (%s) %s' % (name, name, _type[i:])
680
            self.ex('ALTER TABLE "{formdata_table}" ADD CONSTRAINT %s' % constraint)
681
        self.ex('ALTER TABLE "{formdata_table}" ADD PRIMARY KEY (id)')
685
            self.ex('ALTER TABLE {formdata_table} ADD CONSTRAINT %s' % constraint)
686
        self.ex('ALTER TABLE {formdata_table} ADD PRIMARY KEY (id)')
682 687
        # table des evolutions
683
        self.create_table('{evolution_table}', [
688
        self.create_table(self.evolution_table_name, [
684 689
            ['id', 'serial primary key'],
685
            ['status_id', 'smallint REFERENCES "{status_table}" (id)'],
686
            ['formdata_id', 'integer REFERENCES "{formdata_table}" (id)'],
690
            ['status_id', 'smallint REFERENCES {status_table} (id)'],
691
            ['formdata_id', 'integer REFERENCES {formdata_table} (id)'],
687 692
            ['time', 'timestamp'],
688 693
            ['date', 'date'],
689 694
            ['hour_id', 'smallint REFERENCES {hour_table} (id)'],
690 695
        ])
691
        self.ex('COMMENT ON TABLE "{evolution_table}" IS %s',
696
        self.ex('COMMENT ON TABLE {evolution_table} IS %s',
692 697
                vars=(u'evolution des demandes %s' % self.formdef.schema.name,))
693 698

  
694 699
    def insert_item_value(self, field, value):
695
        table_name = self.hash_table_name('{formdata_table}_field_%s' % field.varname)
696
        self.ex('SELECT id FROM "{item_table}" WHERE label = %s',
697
                ctx={'item_table': table_name}, vars=(value,))
700
        table_name = self.hash_table_name('%s_field_%s' % (self.table_name, field.varname))
701
        self.ex('SELECT id FROM {item_table} WHERE label = %s',
702
                ctx={'item_table': quote(table_name)}, vars=(value,))
698 703
        res = self.cur.fetchone()
699 704
        if res:
700 705
            return res[0]
701
        self.ex('INSERT INTO "{item_table}" (label) VALUES (%s) RETURNING (id)', vars=(value,),
702
                ctx={'item_table': table_name})
706
        self.ex('INSERT INTO {item_table} (label) VALUES (%s) RETURNING (id)', vars=(value,),
707
                ctx={'item_table': quote(table_name)})
703 708
        return self.cur.fetchone()[0]
704 709

  
705 710
    def get_item_id(self, field, value):
......
837 842
        if not values:
838 843
            self.logger.warning('no data')
839 844
            return
840
        self.ex('INSERT INTO "{formdata_table}" ({columns}) VALUES {values} RETURNING id',
845
        self.ex('INSERT INTO {formdata_table} ({columns}) VALUES {values} RETURNING id',
841 846
                ctx=dict(columns=', '.join(['"%s"' % column for column in self.columns[1:]]), values=', '.join(values)))
842 847

  
843 848
        # insert generic evolutions
......
864 869
                row[0] = formdata_id
865 870
                evolutions.append(tuple(row))
866 871
                if len(evolutions) == 500:
867
                    self.ex('INSERT INTO "{evolution_table}" (%s) VALUES %s' % (
872
                    self.ex('INSERT INTO {evolution_table} (%s) VALUES %s' % (
868 873
                       ', '.join(['formdata_id', 'status_id', 'time', 'date', 'hour_id']),
869 874
                       ', '.join(['%s'] * len(evolutions))), vars=evolutions)
870 875
                    evolutions = []
871 876
        if evolutions:
872
            self.ex('INSERT INTO "{evolution_table}" (%s) VALUES %s' % (
877
            self.ex('INSERT INTO {evolution_table} (%s) VALUES %s' % (
873 878
                ', '.join(['formdata_id', 'status_id', 'time', 'date', 'hour_id']),
874 879
                ', '.join(['%s'] * len(evolutions))), vars=evolutions)
875 880

  
......
880 885

  
881 886
    def feed(self):
882 887
        self.olap_feeder.ctx.push({
883
            'formdata_table': self.table_name,
884
            'status_table': self.status_table_name,
885
            'evolution_table': self.evolution_table_name
888
            'formdata_table': quote(self.table_name),
889
            'status_table': quote(self.status_table_name),
890
            'evolution_table': quote(self.evolution_table_name),
886 891
        })
887 892

  
888 893
        # create cube
......
892 897
        cube.update({
893 898
            'name': self.table_name,
894 899
            'label': self.formdef.schema.name,
895
            'fact_table': self.table_name,
900
            'fact_table': quote(self.table_name),
896 901
            'key': 'id',
897 902
        })
898 903
        cube['dimensions'] = [dimension for dimension in cube['dimensions']
......
920 925
            cube['joins'].append({
921 926
                'name': at,
922 927
                'table': 'role',
923
                'master': at,
928
                'master': quote(at),
924 929
                'detail': 'id',
925 930
            })
926 931
            cube['dimensions'].append({
......
928 933
                'label': u'fonction %s' % name.lower(),
929 934
                'join': [at],
930 935
                'type': 'integer',
931
                'value': '"%s".id' % at,
932
                'value_label': '"%s".label' % at,
936
                'value': '%s.id' % quote(at),
937
                'value_label': '%s.label' % quote(at),
933 938
                'filter': False,
934 939
            })
935 940

  
......
947 952
            join = None
948 953

  
949 954
            if field.type == 'item':
950
                table_name = self.hash_table_name('{formdata_table}_field_%s' % field.varname)
955
                table_name = self.hash_table_name('%s_field_%s' % (self.table_name, field.varname))
951 956
                join = {
952 957
                    'name': field.varname,
953
                    'table': table_name,
954
                    'master': '"field_%s"' % field.varname,
958
                    'table': quote(table_name),
959
                    'master': quote('field_%s' % field.varname),
955 960
                    'detail': 'id',
956 961
                }
957 962
                if not field.required:
......
961 966
                    'label': field.label.lower(),
962 967
                    'join': [field.varname],
963 968
                    'type': 'integer',
964
                    'value': '"%s".id' % field.varname,
965
                    'value_label': '"%s".label' % field.varname,
969
                    'value': '%s.id' % quote(field.varname),
970
                    'value_label': '%s.label' % quote(field.varname),
966 971
                    'filter': True,
967 972
                }
968 973
            elif field.type == 'bool':
......
970 975
                    'name': field.varname,
971 976
                    'label': field.label.lower(),
972 977
                    'type': 'bool',
973
                    'value': '"field_%s"' % field.varname,
974
                    'value_label': '(CASE WHEN "field_%(varname)s" IS NULL THEN NULL'
975
                                   ' WHEN "field_%(varname)s" THEN \'Oui\''
978
                    'value': quote('field_%s' % field.varname),
979
                    'value_label': '(CASE WHEN %(field)s IS NULL THEN NULL'
980
                                   ' WHEN %(field)s THEN \'Oui\''
976 981
                                   ' ELSE \'Non\' END)' % {
977
                        'varname': field.varname,
982
                        'field': quote('field_%s' % field.varname),
978 983
                    },
979 984
                    'filter': True,
980 985
                }
......
983 988
                    'name': field.varname,
984 989
                    'label': field.label.lower(),
985 990
                    'type': 'string',
986
                    'value': '"field_%s"' % field.varname,
991
                    'value': quote('field_%s' % field.varname),
987 992
                    'filter': True,
988 993
                }
989 994
            else:
990
-