Exemple #1
0
    def create_pd_record(self, owner_org, resource_name):
        lc = LocalCKAN(username=c.user)

        try:
            chromo = h.recombinant_get_chromo(resource_name)
            rcomb = lc.action.recombinant_show(
                owner_org=owner_org, dataset_type=chromo['dataset_type'])
            [res
             ] = [r for r in rcomb['resources'] if r['name'] == resource_name]

            check_access('datastore_upsert', {
                'user': c.user,
                'auth_user_obj': c.userobj
            }, {'resource_id': res['id']})
        except NotAuthorized:
            return abort(403, _('Unauthorized'))

        choice_fields = {
            f['datastore_id']: [{
                'value': k,
                'label': v
            } for (k, v) in f['choices']]
            for f in h.recombinant_choice_fields(resource_name)
        }
        pk_fields = aslist(chromo['datastore_primary_key'])

        if request.method == 'POST':
            post_data = parse_params(request.POST, ignore_keys=['save'])

            if 'cancel' in post_data:
                return redirect(
                    h.url_for(
                        controller=
                        'ckanext.recombinant.controller:UploadController',
                        action='preview_table',
                        resource_name=resource_name,
                        owner_org=rcomb['owner_org'],
                    ))

            data, err = clean_check_type_errors(post_data, chromo['fields'],
                                                pk_fields, choice_fields)
            try:
                lc.action.datastore_upsert(resource_id=res['id'],
                                           method='insert',
                                           records=[{
                                               k: None if k in err else v
                                               for (k, v) in data.items()
                                           }],
                                           dry_run=bool(err))
            except ValidationError as ve:
                if 'records' in ve.error_dict:
                    err = dict(
                        {
                            k: [_(e) for e in v]
                            for (k, v) in ve.error_dict['records'][0].items()
                        }, **err)
                elif ve.error_dict.get('info', {}).get('pgcode',
                                                       '') == '23505':
                    err = dict(
                        {
                            k: [_("This record already exists")]
                            for k in pk_fields
                        }, **err)

            if err:
                return render('recombinant/create_pd_record.html',
                              extra_vars={
                                  'data': data,
                                  'resource_name': resource_name,
                                  'chromo_title': chromo['title'],
                                  'choice_fields': choice_fields,
                                  'owner_org': rcomb['owner_org'],
                                  'errors': err,
                              })

            h.flash_notice(_(u'Record Created'))

            return redirect(
                h.url_for(
                    controller=
                    'ckanext.recombinant.controller:UploadController',
                    action='preview_table',
                    resource_name=resource_name,
                    owner_org=rcomb['owner_org'],
                ))

        return render('recombinant/create_pd_record.html',
                      extra_vars={
                          'data': {},
                          'resource_name': resource_name,
                          'chromo_title': chromo['title'],
                          'choice_fields': choice_fields,
                          'owner_org': rcomb['owner_org'],
                          'errors': {},
                      })
Exemple #2
0
    def update_pd_record(self, owner_org, resource_name, pk):
        pk = [url_part_unescape(p) for p in pk.split(',')]

        lc = LocalCKAN(username=c.user)

        try:
            chromo = h.recombinant_get_chromo(resource_name)
            rcomb = lc.action.recombinant_show(
                owner_org=owner_org, dataset_type=chromo['dataset_type'])
            [res
             ] = [r for r in rcomb['resources'] if r['name'] == resource_name]

            check_access('datastore_upsert', {
                'user': c.user,
                'auth_user_obj': c.userobj
            }, {'resource_id': res['id']})
        except NotAuthorized:
            abort(403, _('Unauthorized'))

        choice_fields = {
            f['datastore_id']: [{
                'value': k,
                'label': v
            } for (k, v) in f['choices']]
            for f in h.recombinant_choice_fields(resource_name)
        }
        pk_fields = aslist(chromo['datastore_primary_key'])
        pk_filter = dict(zip(pk_fields, pk))

        records = lc.action.datastore_search(resource_id=res['id'],
                                             filters=pk_filter)['records']
        if len(records) == 0:
            abort(404, _('Not found'))
        if len(records) > 1:
            abort(400, _('Multiple records found'))
        record = records[0]

        if request.method == 'POST':
            post_data = parse_params(request.POST,
                                     ignore_keys=['save'] + pk_fields)

            if 'cancel' in post_data:
                return redirect(
                    h.url_for(
                        controller=
                        'ckanext.recombinant.controller:UploadController',
                        action='preview_table',
                        resource_name=resource_name,
                        owner_org=rcomb['owner_org'],
                    ))

            data, err = clean_check_type_errors(post_data, chromo['fields'],
                                                pk_fields, choice_fields)
            # can't change pk fields
            for f_id in data:
                if f_id in pk_fields:
                    data[f_id] = record[f_id]
            try:
                lc.action.datastore_upsert(
                    resource_id=res['id'],
                    #method='update',    FIXME not raising ValidationErrors
                    records=[{
                        k: None if k in err else v
                        for (k, v) in data.items()
                    }],
                    dry_run=bool(err))
            except ValidationError as ve:
                err = dict(
                    {
                        k: [_(e) for e in v]
                        for (k, v) in ve.error_dict['records'][0].items()
                    }, **err)

            if err:
                return render('recombinant/update_pd_record.html',
                              extra_vars={
                                  'data': data,
                                  'resource_name': resource_name,
                                  'chromo_title': chromo['title'],
                                  'choice_fields': choice_fields,
                                  'pk_fields': pk_fields,
                                  'owner_org': rcomb['owner_org'],
                                  'errors': err,
                              })

            h.flash_notice(_(u'Record %s Updated') % u','.join(pk))

            return redirect(
                h.url_for(
                    controller=
                    'ckanext.recombinant.controller:UploadController',
                    action='preview_table',
                    resource_name=resource_name,
                    owner_org=rcomb['owner_org'],
                ))

        data = {}
        for f in chromo['fields']:
            if not f.get('import_template_include', True):
                continue
            val = record[f['datastore_id']]
            data[f['datastore_id']] = val

        return render('recombinant/update_pd_record.html',
                      extra_vars={
                          'data': data,
                          'resource_name': resource_name,
                          'chromo_title': chromo['title'],
                          'choice_fields': choice_fields,
                          'pk_fields': pk_fields,
                          'owner_org': rcomb['owner_org'],
                          'errors': {},
                      })
    def update_pd_record(self, owner_org, resource_name, pk):
        pk = [url_part_unescape(p) for p in pk.split(',')]

        lc = LocalCKAN(username=c.user)

        try:
            chromo = h.recombinant_get_chromo(resource_name)
            rcomb = lc.action.recombinant_show(
                owner_org=owner_org,
                dataset_type=chromo['dataset_type'])
            [res] = [r for r in rcomb['resources'] if r['name'] == resource_name]

            check_access(
                'datastore_upsert',
                {'user': c.user, 'auth_user_obj': c.userobj},
                {'resource_id': res['id']})
        except NotAuthorized:
            abort(403, _('Unauthorized'))

        choice_fields = {
            f['datastore_id']: [
                {'value': k, 'label': v} for (k, v) in f['choices']]
            for f in h.recombinant_choice_fields(resource_name)}
        pk_fields = aslist(chromo['datastore_primary_key'])
        pk_filter = dict(zip(pk_fields, pk))

        records = lc.action.datastore_search(
            resource_id=res['id'],
            filters=pk_filter)['records']
        if len(records) == 0:
            abort(404, _('Not found'))
        if len(records) > 1:
            abort(400, _('Multiple records found'))
        record = records[0]

        if request.method == 'POST':
            post_data = parse_params(request.POST, ignore_keys=['save'] + pk_fields)

            if 'cancel' in post_data:
                return redirect(h.url_for(
                    controller='ckanext.recombinant.controller:UploadController',
                    action='preview_table',
                    resource_name=resource_name,
                    owner_org=rcomb['owner_org'],
                    ))

            data = {}
            for f in chromo['fields']:
                f_id = f['datastore_id']
                if not f.get('import_template_include', True):
                    continue
                if f_id in pk_fields:
                    data[f_id] = record[f_id]
                else:
                    val = post_data.get(f['datastore_id'], '')
                    if isinstance(val, list):
                        val = u','.join(val)
                    val = canonicalize(
                        val,
                        f['datastore_type'],
                        primary_key=False,
                        choice_field=f_id in choice_fields)
                    data[f['datastore_id']] = val
            try:
                lc.action.datastore_upsert(
                    resource_id=res['id'],
                    #method='update',    FIXME not raising ValidationErrors
                    records=[data])
            except ValidationError as ve:
                err = {
                    k: [_(e) for e in v]
                    for (k, v) in ve.error_dict['records'][0].items()}
                return render('recombinant/update_pd_record.html',
                    extra_vars={
                        'data': data,
                        'resource_name': resource_name,
                        'chromo_title': chromo['title'],
                        'choice_fields': choice_fields,
                        'pk_fields': pk_fields,
                        'owner_org': rcomb['owner_org'],
                        'errors': err,
                        })

            h.flash_notice(_(u'Record %s Updated') % u','.join(pk) )

            return redirect(h.url_for(
                controller='ckanext.recombinant.controller:UploadController',
                action='preview_table',
                resource_name=resource_name,
                owner_org=rcomb['owner_org'],
                ))

        data = {}
        for f in chromo['fields']:
            if not f.get('import_template_include', True):
                continue
            val = record[f['datastore_id']]
            data[f['datastore_id']] = val

        return render('recombinant/update_pd_record.html',
            extra_vars={
                'data': data,
                'resource_name': resource_name,
                'chromo_title': chromo['title'],
                'choice_fields': choice_fields,
                'pk_fields': pk_fields,
                'owner_org': rcomb['owner_org'],
                'errors': {},
                })
def update_triggers():
    """Create/update triggers used by PD tables"""

    lc = LocalCKAN()

    # *_error functions return NULL or ARRAY[[field_name, error_message]]
    lc.action.datastore_function_create(
        name=u'required_error',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        rettype=u'_text',
        definition=u'''
            BEGIN
                IF (value = '') IS NOT FALSE THEN
                    RETURN ARRAY[[field_name, 'This field must not be empty']];
                END IF;
                RETURN NULL;
            END;
        ''')
    lc.action.datastore_function_create(
        name=u'required_error',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'_text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        rettype=u'_text',
        definition=u'''
            BEGIN
                IF value IS NULL OR value = '{}' THEN
                    return ARRAY[[field_name, 'This field must not be empty']];
                END IF;
                RETURN NULL;
            END;
        ''')
    lc.action.datastore_function_create(
        name=u'required_error',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'date'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        rettype=u'_text',
        definition=u'''
            BEGIN
                IF value IS NULL THEN
                    RETURN ARRAY[[field_name, 'This field must not be empty']];
                END IF;
                RETURN NULL;
            END;
        ''')
    lc.action.datastore_function_create(
        name=u'choice_error',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'text'},
            {u'argname': u'choices', u'argtype': u'_text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        rettype=u'_text',
        definition=ur'''
            BEGIN
                IF NOT (value = ANY (choices)) THEN
                    -- \t is used when converting errors to string
                    RETURN ARRAY[[field_name, 'Invalid choice: "'
                        || replace(value, E'\t', ' ') || '"']];
                END IF;
                RETURN NULL;
            END;
        ''')
    # return record with .clean (normalized value) and .error
    # (NULL or ARRAY[[field_name, error_message]])
    lc.action.datastore_function_create(
        name=u'choices_clean_error',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'_text'},
            {u'argname': u'choices', u'argtype': u'_text'},
            {u'argname': u'field_name', u'argtype': u'text'},
            {u'argname': u'clean', u'argtype': u'_text', u'argmode': u'out'},
            {u'argname': u'error', u'argtype': u'_text', u'argmode': u'out'}],
        rettype=u'record',
        definition=ur'''
            DECLARE
                bad_choices text := array_to_string(ARRAY(
                    SELECT unnest(value)
                    EXCEPT SELECT unnest(choices)), ', ');
            BEGIN
                IF bad_choices <> '' THEN
                    -- \t is used when converting errors to string
                    error := ARRAY[[field_name, 'Invalid choice: "'
                        || replace(bad_choices, E'\t', ' ') || '"']];
                END IF;
                clean := ARRAY(
                    SELECT c FROM(SELECT unnest(choices) as c) u
                    WHERE c in (SELECT unnest(value)));
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'not_empty',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF (value = '') IS NOT FALSE THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'not_empty',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'date'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value IS NULL THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'not_empty',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'_text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value IS NULL OR value = '{}' THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'not_empty',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'int4'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value IS NULL THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'not_empty',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'money'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value IS NULL THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'not_empty',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'numeric'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value IS NULL THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'only_empty',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'numeric'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value IS NOT NULL THEN
                    RAISE EXCEPTION 'This field must be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'no_surrounding_whitespace',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=ur'''
            BEGIN
                IF trim(both E'\t\n\x0b\x0c\r ' from value) <> value THEN
                    RAISE EXCEPTION 'This field must not have surrounding whitespace: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'year_optional_month_day',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            DECLARE
                ymd _text := regexp_matches(value,
                    '(\d\d\d\d)(?:-(\d\d)(?:-(\d\d))?)?');
            BEGIN
                IF ymd IS NULL THEN
                    RAISE EXCEPTION 'Dates must be in YYYY-MM-DD format: %', field_name;
                END IF;
                IF ymd[3] IS NOT NULL THEN
                    PERFORM value::date;
                ELSIF NOT ymd[2]::int BETWEEN 1 AND 12 THEN
                    RAISE EXCEPTION 'Dates must be in YYYY-MM-DD format: %', field_name;
                END IF;
            EXCEPTION
                WHEN others THEN
                    RAISE EXCEPTION 'Dates must be in YYYY-MM-DD format: %', field_name;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'choice_one_of',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'text'},
            {u'argname': u'choices', u'argtype': u'_text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF NOT (value = ANY (choices)) THEN
                    RAISE EXCEPTION 'Invalid choice for %: "%"', field_name, value;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'choices_from',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'_text'},
            {u'argname': u'choices', u'argtype': u'_text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        rettype=u'_text',
        definition=u'''
            DECLARE
                bad_choices text := array_to_string(ARRAY(
                    SELECT unnest(value)
                    EXCEPT SELECT unnest(choices)), ', ');
            BEGIN
                IF bad_choices <> '' THEN
                    RAISE EXCEPTION 'Invalid choice for %: "%"', field_name, bad_choices;
                END IF;
                RETURN ARRAY(
                    SELECT c FROM(SELECT unnest(choices) as c) u
                    WHERE c in (SELECT unnest(value)));
            END;
        ''')

    # A: When sysadmin passes '*' as user_modified, replace with '' and
    #    set created+modified values to NULL. This is used when restoring
    #    earlier migrated data that had no record of the
    #    user/created/modified values
    # B: Otherwise update created+modified dates and replace user with
    #    current user
    lc.action.datastore_function_create(
        name=u'update_record_modified_created_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            DECLARE
                req_user_modified text := NEW.user_modified;
                username text NOT NULL := (SELECT username
                    FROM datastore_user);
                sysadmin boolean NOT NULL := (SELECT sysadmin
                    FROM datastore_user);
            BEGIN
                IF NOT sysadmin THEN
                    req_user_modified := NULL;
                END IF;
                IF TG_OP = 'INSERT' THEN
                    IF req_user_modified = '*' THEN
                        NEW.user_modified := '';
                        NEW.record_created := NULL;
                        NEW.record_modified := NULL;
                        RETURN NEW;
                    END IF;
                    IF NEW.record_created IS NULL THEN
                        NEW.record_created := now() at time zone 'utc';
                    END IF;
                    IF NEW.record_modified IS NULL THEN
                        NEW.record_modified := NEW.record_created;
                    END IF;
                    IF (req_user_modified = '') IS NOT FALSE THEN
                        NEW.user_modified := username;
                    END IF;
                    RETURN NEW;
                END IF;

                IF req_user_modified = '*' THEN
                    NEW.user_modified := '';
                    NEW.record_created := NULL;
                    NEW.record_modified := NULL;
                    IF OLD = NEW THEN
                        RETURN NULL;
                    END IF;
                    RETURN NEW;
                END IF;

                IF NEW.record_created IS NULL THEN
                    NEW.record_created := OLD.record_created;
                END IF;
                IF NEW.record_modified IS NULL THEN
                    NEW.record_modified := OLD.record_modified;
                END IF;
                IF (req_user_modified = '') IS NOT FALSE THEN
                    NEW.user_modified := OLD.user_modified;
                END IF;
                IF OLD = NEW THEN
                    RETURN NULL;
                END IF;
                NEW.record_modified := now() at time zone 'utc';
                IF (req_user_modified = '') IS NOT FALSE THEN
                    NEW.user_modified := username;
                ELSE
                    NEW.user_modified := req_user_modified;
                END IF;
                RETURN NEW;
            END;
            ''')

    inventory_choices = dict(
        (f['datastore_id'], f['choices'])
        for f in h.recombinant_choice_fields('inventory'))
    lc.action.datastore_function_create(
        name=u'inventory_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.ref_number, 'ref_number');
                PERFORM no_surrounding_whitespace(NEW.ref_number, 'ref_number');
                PERFORM not_empty(NEW.title_en, 'title_en');
                PERFORM not_empty(NEW.title_fr, 'title_fr');
                -- PERFORM not_empty(NEW.description_en, 'description_en');
                -- PERFORM not_empty(NEW.description_fr, 'description_fr');
                -- PERFORM not_empty(NEW.date_published, 'date_published');
                -- PERFORM year_optional_month_day(NEW.date_published, 'date_published');
                -- PERFORM not_empty(NEW.language, 'language');
                -- PERFORM choice_one_of(NEW.language, {language}, 'language');
                -- PERFORM not_empty(NEW.size, 'size');
                NEW.eligible_for_release := truthy_to_yn(NEW.eligible_for_release);
                -- PERFORM not_empty(NEW.eligible_for_release, 'eligible_for_release');
                -- PERFORM choice_one_of(NEW.eligible_for_release, {eligible_for_release}, 'eligible_for_release');
                -- PERFORM not_empty(NEW.program_alignment_architecture_en, 'program_alignment_architecture_en');
                -- PERFORM not_empty(NEW.program_alignment_architecture_fr, 'program_alignment_architecture_fr');
                -- PERFORM not_empty(NEW.date_released, 'date_released');
                -- PERFORM year_optional_month_day(NEW.date_released, 'date_released');
                RETURN NEW;
            END;
            '''.format(
                language=pg_array(inventory_choices['language']),
                eligible_for_release=pg_array(inventory_choices['eligible_for_release']),
            )
        )

    lc.action.datastore_function_create(
        name=u'protect_user_votes_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            DECLARE
                req_user_votes int := NEW.user_votes;
                sysadmin boolean NOT NULL := (SELECT sysadmin
                    FROM datastore_user);
            BEGIN
                IF NOT sysadmin THEN
                    req_user_votes := NULL;
                END IF;

                IF req_user_votes IS NULL AND TG_OP = 'UPDATE' THEN
                    NEW.user_votes := OLD.user_votes;
                ELSE
                    NEW.user_votes = req_user_votes;
                END IF;

                IF NEW.user_votes IS NULL THEN
                    NEW.user_votes := 0;
                END IF;
                RETURN NEW;
            END;
            ''')

    lc.action.datastore_function_create(
        name=u'truthy_to_yn',
        or_replace=True,
        arguments=[{u'argname': u'value', u'argtype': u'text'}],
        rettype=u'text',
        definition=u'''
            DECLARE
                truthy boolean := value ~*
                    '[[:<:]](true|t|vrai|v|1|yes|y|oui|o)[[:>:]]';
                falsy boolean := value ~*
                    '[[:<:]](false|f|faux|0|no|n|non)[[:>:]]';
            BEGIN
                IF truthy AND NOT falsy THEN
                    RETURN 'Y';
                ELSIF falsy AND NOT truthy THEN
                    RETURN 'N';
                ELSE
                    RETURN NULL;
                END IF;
            END;
            ''')

    lc.action.datastore_function_create(
        name=u'integer_or_na_nd',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value <> 'NA' AND value <> 'ND' AND NOT value ~ '^[0-9]+$' THEN
                    RAISE EXCEPTION 'This field must be NA or an integer: %', field_name;
                END IF;
            END;
        ''')

    grants_choices = dict(
        (f['datastore_id'], f['choices'])
        for f in h.recombinant_choice_fields('grants'))
    lc.action.datastore_function_create(
        name=u'grants_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.ref_number, 'ref_number');

                PERFORM not_empty(NEW.amendment_number, 'amendment_number');
                IF NEW.amendment_number <> 0 THEN
                    PERFORM not_empty(NEW.amendment_date, 'amendment_date');
                END IF;

                IF NOT ((NEW.foreign_currency_type = '') IS NOT FALSE) OR
                        NEW.foreign_currency_value IS NOT NULL THEN
                    PERFORM not_empty(NEW.foreign_currency_type, 'foreign_currency_type');
                    PERFORM choice_one_of(
                        NEW.foreign_currency_type,
                        {foreign_currency_type},
                        'foreign_currency_type');
                    PERFORM not_empty(NEW.foreign_currency_value, 'foreign_currency_value');
                END IF;

                PERFORM not_empty(NEW.agreement_value, 'agreement_value');

                PERFORM not_empty(NEW.agreement_start_date, 'agreement_start_date');
                IF NEW.agreement_start_date >= '2018-04-01'::date THEN
                    PERFORM not_empty(NEW.agreement_type, 'agreement_type');
                    PERFORM choice_one_of(
                        NEW.agreement_type,
                        {agreement_type},
                        'agreement_type');
                    IF NOT ((NEW.recipient_type = '') IS NOT FALSE) THEN
                        PERFORM choice_one_of(
                            NEW.recipient_type,
                            {recipient_type},
                            'recipient_type');
                    END IF;
                    PERFORM not_empty(NEW.recipient_legal_name, 'recipient_legal_name');
                    PERFORM not_empty(NEW.recipient_country, 'recipient_country');
                    PERFORM choice_one_of(
                        NEW.recipient_country,
                        {recipient_country},
                        'recipient_country');
                    IF NEW.recipient_country = 'CA' THEN
                        PERFORM not_empty(NEW.recipient_province, 'recipient_province');
                        PERFORM choice_one_of(
                            NEW.recipient_province,
                            {recipient_province},
                            'recipient_province');
                    END IF;
                    PERFORM not_empty(NEW.recipient_city, 'recipient_city');
                    PERFORM not_empty(NEW.description_en, 'description_en');
                    PERFORM not_empty(NEW.description_fr, 'description_fr');
                END IF;
                RETURN NEW;
            END;
            '''.format(
                agreement_type=pg_array(grants_choices['agreement_type']),
                recipient_type=pg_array(grants_choices['recipient_type']),
                recipient_country=pg_array(grants_choices['recipient_country']),
                recipient_province=pg_array(grants_choices['recipient_province']),
                foreign_currency_type=pg_array(grants_choices['foreign_currency_type']),
            )
        )

    grants_nil_choices = dict(
        (f['datastore_id'], f['choices'])
        for f in h.recombinant_choice_fields('grants-nil'))
    lc.action.datastore_function_create(
        name=u'grants_nil_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.fiscal_year, 'fiscal_year');
                PERFORM choice_one_of(NEW.fiscal_year, {fiscal_year}, 'fiscal_year');
                PERFORM not_empty(NEW.quarter, 'quarter');
                PERFORM choice_one_of(NEW.quarter, {quarter}, 'quarter');
                RETURN NEW;
            END;
            '''.format(
                fiscal_year=pg_array(grants_nil_choices['fiscal_year']),
                quarter=pg_array(grants_nil_choices['quarter']),
            )
        )
Exemple #5
0
    def update_triggers(self):
        """Create/update triggers used by PD tables"""
        lc = LocalCKAN()
        choices = dict((f['datastore_id'], f['choices'])
                       for f in h.recombinant_choice_fields('consultations'))
        lc.action.datastore_function_create(
            name=u'consultations_trigger',
            or_replace=True,
            rettype=u'trigger',
            definition=u'''
                DECLARE
                    bad_partner_departments text := array_to_string(ARRAY(
                        SELECT unnest(NEW.partner_departments)
                        EXCEPT SELECT unnest({partner_departments})), ', ');
                    bad_subjects text := array_to_string(ARRAY(
                        SELECT unnest(NEW.subjects)
                        EXCEPT SELECT unnest({subjects})), ', ');
                    bad_goals text := array_to_string(ARRAY(
                        SELECT unnest(NEW.goals)
                        EXCEPT SELECT unnest({goals})), ', ');
                    bad_target_participants_and_audience text := array_to_string(ARRAY(
                        SELECT unnest(NEW.target_participants_and_audience)
                        EXCEPT SELECT unnest({target_participants_and_audience})), ', ');
                    bad_rationale text := array_to_string(ARRAY(
                        SELECT unnest(NEW.rationale)
                        EXCEPT SELECT unnest({rationale})), ', ');
                BEGIN
                    IF (NEW.registration_number = '') IS NOT FALSE THEN
                        RAISE EXCEPTION 'This field must not be empty: registration_number';
                    END IF;

                    IF NOT (NEW.publishable = ANY {publishable}) THEN
                        RAISE EXCEPTION 'Invalid choice for publishable: "%"', NEW.publishable;
                    END IF;

                    IF bad_partner_departments <> '' THEN
                        RAISE EXCEPTION 'Invalid choice for partner_departments: "%"', bad_partner_departments;
                    END IF;
                    NEW.partner_departments := ARRAY(
                        SELECT c FROM(SELECT unnest({partner_departments}) as c) u
                        WHERE c in (SELECT unnest(NEW.partner_departments)));

                    IF NOT (NEW.sector = ANY {sectors}) THEN
                        RAISE EXCEPTION 'Invalid choice for sector: "%"', NEW.sector;
                    END IF;

                    IF NEW.subjects = '{{}}' THEN
                        RAISE EXCEPTION 'This field must not be empty: subjects';
                    END IF;
                    IF bad_subjects <> '' THEN
                        RAISE EXCEPTION 'Invalid choice for subjects: "%"', bad_subjects;
                    END IF;
                    NEW.subjects := ARRAY(
                        SELECT c FROM(SELECT unnest({subjects}) as c) u
                        WHERE c in (SELECT unnest(NEW.subjects)));

                    IF (NEW.title_en = '') IS NOT FALSE THEN
                        RAISE EXCEPTION 'This field must not be empty: title_en';
                    END IF;

                    IF (NEW.title_fr = '') IS NOT FALSE THEN
                        RAISE EXCEPTION 'This field must not be empty: title_fr';
                    END IF;

                    IF NEW.goals = '{{}}' THEN
                        RAISE EXCEPTION 'This field must not be empty: goals';
                    END IF;
                    IF bad_goals <> '' THEN
                        RAISE EXCEPTION 'Invalid choice for goals: "%"', bad_goals;
                    END IF;
                    NEW.goals := ARRAY(
                        SELECT c FROM(SELECT unnest({goals}) as c) u
                        WHERE c in (SELECT unnest(NEW.goals)));

                    IF (NEW.description_en = '') IS NOT FALSE THEN
                        RAISE EXCEPTION 'This field must not be empty: description_en';
                    END IF;

                    IF (NEW.description_fr = '') IS NOT FALSE THEN
                        RAISE EXCEPTION 'This field must not be empty: description_fr';
                    END IF;

                    IF NOT (NEW.public_opinion_research = ANY {public_opinion_research}) THEN
                        RAISE EXCEPTION 'Invalid choice for public_opinion_research: "%"', NEW.public_opinion_research;
                    END IF;

                    IF NOT (NEW.public_opinion_research_standing_offer = ANY {public_opinion_research_standing_offer}) THEN
                        RAISE EXCEPTION 'Invalid choice for public_opinion_research_standing_offer: "%"', NEW.public_opinion_research_standing_offer;
                    END IF;

                    IF NEW.target_participants_and_audience = '{{}}' THEN
                        RAISE EXCEPTION 'This field must not be empty: target_participants_and_audience';
                    END IF;
                    IF bad_target_participants_and_audience <> '' THEN
                        RAISE EXCEPTION 'Invalid choice for target_participants_and_audience: "%"', bad_target_participants_and_audience;
                    END IF;
                    NEW.target_participants_and_audience := ARRAY(
                        SELECT c FROM(SELECT unnest({target_participants_and_audience}) as c) u
                        WHERE c in (SELECT unnest(NEW.target_participants_and_audience)));

                    IF NEW.planned_start_date IS NULL THEN
                        RAISE EXCEPTION 'This field must not be empty: planned_start_date';
                    END IF;

                    IF NEW.planned_end_date IS NULL THEN
                        RAISE EXCEPTION 'This field must not be empty: planned_end_date';
                    END IF;

                    IF NOT (NEW.status = ANY {status}) THEN
                        RAISE EXCEPTION 'Invalid choice for status: "%"', NEW.status;
                    END IF;

                    IF (NEW.further_information_en = '') IS NOT FALSE THEN
                        RAISE EXCEPTION 'This field must not be empty: further_information_en';
                    END IF;

                    IF (NEW.further_information_fr = '') IS NOT FALSE THEN
                        RAISE EXCEPTION 'This field must not be empty: further_information_fr';
                    END IF;

                    IF NOT (NEW.report_available_online = ANY {report_available_online}) THEN
                        RAISE EXCEPTION 'Invalid choice for report_available_online: "%"', NEW.report_available_online;
                    END IF;

                    IF NEW.rationale = '{{}}' THEN
                        RAISE EXCEPTION 'This field must not be empty: rationale';
                    END IF;
                    IF bad_rationale <> '' THEN
                        RAISE EXCEPTION 'Invalid choice for rationale: "%"', bad_rationale;
                    END IF;
                    NEW.rationale := ARRAY(
                        SELECT c FROM(SELECT unnest({rationale}) as c) u
                        WHERE c in (SELECT unnest(NEW.rationale)));

                    RETURN NEW;
                END;
                '''.format(
                sectors=pg_array(choices['sector']),
                publishable=pg_array(choices['publishable']),
                partner_departments=pg_array(choices['partner_departments']),
                subjects=pg_array(choices['subjects']),
                goals=pg_array(choices['goals']),
                target_participants_and_audience=pg_array(
                    choices['target_participants_and_audience']),
                public_opinion_research=pg_array(
                    choices['public_opinion_research']),
                public_opinion_research_standing_offer=pg_array(
                    choices['public_opinion_research_standing_offer']),
                status=pg_array(choices['status']),
                report_available_online=pg_array(
                    choices['report_available_online']),
                rationale=pg_array(choices['rationale']),
            ))
        lc.action.datastore_function_create(
            name=u'update_record_modified_created_trigger',
            or_replace=True,
            rettype=u'trigger',
            definition=u'''
                DECLARE
                    req_user_modified text := NEW.user_modified;
                    username text NOT NULL := (SELECT username
                        FROM datastore_user);
                    sysadmin boolean NOT NULL := (SELECT sysadmin
                        FROM datastore_user);
                BEGIN
                    IF NOT sysadmin OR (req_user_modified = '') IS NOT FALSE THEN
                        req_user_modified := NULL;
                    END IF;
                    IF TG_OP = 'INSERT' THEN
                        IF NEW.record_created IS NULL THEN
                            NEW.record_created := now() at time zone 'utc';
                        END IF;
                        IF NEW.record_modified IS NULL THEN
                            NEW.record_modified := NEW.record_created;
                        END IF;
                        IF req_user_modified IS NULL THEN
                            NEW.user_modified := username;
                        END IF;
                        RETURN NEW;
                    END IF;

                    IF NEW.record_created IS NULL THEN
                        NEW.record_created := OLD.record_created;
                    END IF;
                    IF NEW.record_modified IS NULL THEN
                        NEW.record_modified := OLD.record_modified;
                    END IF;
                    IF req_user_modified IS NULL THEN
                        NEW.user_modified := OLD.user_modified;
                    END IF;
                    IF OLD = NEW THEN
                        RETURN NULL;
                    END IF;
                    NEW.record_modified := now() at time zone 'utc';
                    IF req_user_modified IS NULL THEN
                        NEW.user_modified := username;
                    ELSE
                        NEW.user_modified := req_user_modified;
                    END IF;
                    RETURN NEW;
                END;
                ''')
Exemple #6
0
def update_triggers():
    """Create/update triggers used by PD tables"""

    lc = LocalCKAN()

    lc.action.datastore_function_create(
        name=u'not_empty',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF (value = '') IS NOT FALSE THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'not_empty',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'date'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value IS NULL THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'not_empty',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'_text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value = '{}' THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'not_empty',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'int4'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value IS NULL THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'not_empty',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'money'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value IS NULL THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'no_surrounding_whitespace',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=ur'''
            BEGIN
                IF trim(both E'\t\n\x0b\x0c\r ' from value) <> value THEN
                    RAISE EXCEPTION 'This field must not have surrounding whitespace: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'year_optional_month_day',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            DECLARE
                ymd _text := regexp_matches(value,
                    '(\d\d\d\d)(?:-(\d\d)(?:-(\d\d))?)?');
            BEGIN
                IF ymd IS NULL THEN
                    RAISE EXCEPTION 'Dates must be in YYYY-MM-DD format: %', field_name;
                END IF;
                IF ymd[3] IS NOT NULL THEN
                    PERFORM value::date;
                ELSIF NOT ymd[2]::int BETWEEN 1 AND 12 THEN
                    RAISE EXCEPTION 'Dates must be in YYYY-MM-DD format: %', field_name;
                END IF;
            EXCEPTION
                WHEN others THEN
                    RAISE EXCEPTION 'Dates must be in YYYY-MM-DD format: %', field_name;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'choice_one_of',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'text'},
            {u'argname': u'choices', u'argtype': u'_text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF NOT (value = ANY (choices)) THEN
                    RAISE EXCEPTION 'Invalid choice for %: "%"', field_name, value;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'choices_from',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'_text'},
            {u'argname': u'choices', u'argtype': u'_text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        rettype=u'_text',
        definition=u'''
            DECLARE
                bad_choices text := array_to_string(ARRAY(
                    SELECT unnest(value)
                    EXCEPT SELECT unnest(choices)), ', ');
            BEGIN
                IF bad_choices <> '' THEN
                    RAISE EXCEPTION 'Invalid choice for %: "%"', field_name, bad_choices;
                END IF;
                RETURN ARRAY(
                    SELECT c FROM(SELECT unnest(choices) as c) u
                    WHERE c in (SELECT unnest(value)));
            END;
        ''')

    consultations_choices = dict(
        (f['datastore_id'], f['choices'])
        for f in h.recombinant_choice_fields('consultations'))
    lc.action.datastore_function_create(
        name=u'consultations_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.registration_number, 'registration_number');
                PERFORM choice_one_of(NEW.publishable, {publishable}, 'publishable');
                NEW.partner_departments := choices_from(
                    NEW.partner_departments, {partner_departments}, 'partner_departments');
                PERFORM not_empty(NEW.subjects, 'subjects');
                NEW.subjects := choices_from(
                    NEW.subjects, {subjects}, 'subjects');
                PERFORM not_empty(NEW.title_en, 'title_en');
                PERFORM not_empty(NEW.title_fr, 'title_fr');
                PERFORM not_empty(NEW.description_en, 'description_en');
                PERFORM not_empty(NEW.description_fr, 'description_fr');
                PERFORM not_empty(
                    NEW.target_participants_and_audience,
                    'target_participants_and_audience');
                NEW.target_participants_and_audience := choices_from(
                    NEW.target_participants_and_audience,
                    {target_participants_and_audience},
                    'target_participants_and_audience');
                PERFORM not_empty(NEW.start_date, 'start_date');
                PERFORM not_empty(NEW.end_date, 'end_date');
                PERFORM choice_one_of(NEW.status, {status}, 'status');
                PERFORM not_empty(NEW.profile_page_en, 'profile_page_en');
                PERFORM not_empty(NEW.profile_page_fr, 'profile_page_fr');
                PERFORM choice_one_of(
                    NEW.report_available_online,
                    {report_available_online},
                    'report_available_online');
                PERFORM not_empty(NEW.high_profile, 'high_profile');
                PERFORM choice_one_of(
                    NEW.high_profile,
                    {high_profile},
                    'high_profile');
                IF NEW.high_profile = 'Y' THEN
                    PERFORM not_empty(NEW.rationale, 'rationale');
                END IF;
                NEW.rationale := choices_from(
                    NEW.rationale, {rationale}, 'rationale');

                RETURN NEW;
            END;
            '''.format(
                publishable=pg_array(consultations_choices['publishable']),
                partner_departments=pg_array(
                    consultations_choices['partner_departments']),
                subjects=pg_array(consultations_choices['subjects']),
                target_participants_and_audience=pg_array(
                    consultations_choices['target_participants_and_audience']),
                status=pg_array(consultations_choices['status']),
                report_available_online=pg_array(
                    consultations_choices['report_available_online']),
                high_profile=pg_array(consultations_choices['high_profile']),
                rationale=pg_array(consultations_choices['rationale']),
            )
        )

    # A: When sysadmin passes '*' as user_modified, replace with '' and
    #    set created+modified values to NULL. This is used when restoring
    #    earlier migrated data that had no record of the
    #    user/created/modified values
    # B: Otherwise update created+modified dates and replace user with
    #    current user
    lc.action.datastore_function_create(
        name=u'update_record_modified_created_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            DECLARE
                req_user_modified text := NEW.user_modified;
                username text NOT NULL := (SELECT username
                    FROM datastore_user);
                sysadmin boolean NOT NULL := (SELECT sysadmin
                    FROM datastore_user);
            BEGIN
                IF NOT sysadmin THEN
                    req_user_modified := NULL;
                END IF;
                IF TG_OP = 'INSERT' THEN
                    IF req_user_modified = '*' THEN
                        NEW.user_modified := '';
                        NEW.record_created := NULL;
                        NEW.record_modified := NULL;
                        RETURN NEW;
                    END IF;
                    IF NEW.record_created IS NULL THEN
                        NEW.record_created := now() at time zone 'utc';
                    END IF;
                    IF NEW.record_modified IS NULL THEN
                        NEW.record_modified := NEW.record_created;
                    END IF;
                    IF (req_user_modified = '') IS NOT FALSE THEN
                        NEW.user_modified := username;
                    END IF;
                    RETURN NEW;
                END IF;

                IF req_user_modified = '*' THEN
                    NEW.user_modified := '';
                    NEW.record_created := NULL;
                    NEW.record_modified := NULL;
                    IF OLD = NEW THEN
                        RETURN NULL;
                    END IF;
                    RETURN NEW;
                END IF;

                IF NEW.record_created IS NULL THEN
                    NEW.record_created := OLD.record_created;
                END IF;
                IF NEW.record_modified IS NULL THEN
                    NEW.record_modified := OLD.record_modified;
                END IF;
                IF (req_user_modified = '') IS NOT FALSE THEN
                    NEW.user_modified := OLD.user_modified;
                END IF;
                IF OLD = NEW THEN
                    RETURN NULL;
                END IF;
                NEW.record_modified := now() at time zone 'utc';
                IF (req_user_modified = '') IS NOT FALSE THEN
                    NEW.user_modified := username;
                ELSE
                    NEW.user_modified := req_user_modified;
                END IF;
                RETURN NEW;
            END;
            ''')

    inventory_choices = dict(
        (f['datastore_id'], f['choices'])
        for f in h.recombinant_choice_fields('inventory'))
    lc.action.datastore_function_create(
        name=u'inventory_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.ref_number, 'ref_number');
                PERFORM no_surrounding_whitespace(NEW.ref_number, 'ref_number');
                PERFORM not_empty(NEW.title_en, 'title_en');
                PERFORM not_empty(NEW.title_fr, 'title_fr');
                -- PERFORM not_empty(NEW.description_en, 'description_en');
                -- PERFORM not_empty(NEW.description_fr, 'description_fr');
                -- PERFORM not_empty(NEW.date_published, 'date_published');
                -- PERFORM year_optional_month_day(NEW.date_published, 'date_published');
                -- PERFORM not_empty(NEW.language, 'language');
                -- PERFORM choice_one_of(NEW.language, {language}, 'language');
                -- PERFORM not_empty(NEW.size, 'size');
                NEW.eligible_for_release := truthy_to_yn(NEW.eligible_for_release);
                -- PERFORM not_empty(NEW.eligible_for_release, 'eligible_for_release');
                -- PERFORM choice_one_of(NEW.eligible_for_release, {eligible_for_release}, 'eligible_for_release');
                -- PERFORM not_empty(NEW.program_alignment_architecture_en, 'program_alignment_architecture_en');
                -- PERFORM not_empty(NEW.program_alignment_architecture_fr, 'program_alignment_architecture_fr');
                -- PERFORM not_empty(NEW.date_released, 'date_released');
                -- PERFORM year_optional_month_day(NEW.date_released, 'date_released');
                RETURN NEW;
            END;
            '''.format(
                language=pg_array(inventory_choices['language']),
                eligible_for_release=pg_array(inventory_choices['eligible_for_release']),
            )
        )

    lc.action.datastore_function_create(
        name=u'protect_user_votes_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            DECLARE
                req_user_votes int := NEW.user_votes;
                sysadmin boolean NOT NULL := (SELECT sysadmin
                    FROM datastore_user);
            BEGIN
                IF NOT sysadmin THEN
                    req_user_votes := NULL;
                END IF;

                IF req_user_votes IS NULL AND TG_OP = 'UPDATE' THEN
                    NEW.user_votes := OLD.user_votes;
                ELSE
                    NEW.user_votes = req_user_votes;
                END IF;

                IF NEW.user_votes IS NULL THEN
                    NEW.user_votes := 0;
                END IF;
                RETURN NEW;
            END;
            ''')

    lc.action.datastore_function_create(
        name=u'truthy_to_yn',
        or_replace=True,
        arguments=[{u'argname': u'value', u'argtype': u'text'}],
        rettype=u'text',
        definition=u'''
            DECLARE
                truthy boolean := value ~*
                    '[[:<:]](true|t|vrai|v|1|yes|y|oui|o)[[:>:]]';
                falsy boolean := value ~*
                    '[[:<:]](false|f|faux|0|no|n|non)[[:>:]]';
            BEGIN
                IF truthy AND NOT falsy THEN
                    RETURN 'Y';
                ELSIF falsy AND NOT truthy THEN
                    RETURN 'N';
                ELSE
                    RETURN NULL;
                END IF;
            END;
            ''')

    lc.action.datastore_function_create(
        name=u'contracts_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.reference_number, 'reference_number');
                NEW.aboriginal_business := truthy_to_yn(NEW.aboriginal_business);
                NEW.potential_commercial_exploitation := truthy_to_yn(NEW.potential_commercial_exploitation);
                NEW.former_public_servant := truthy_to_yn(NEW.former_public_servant);
                RETURN NEW;
            END;
            ''')

    lc.action.datastore_function_create(
        name=u'valid_percentage',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'int4'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value < 0 OR value > 100 THEN
                    RAISE EXCEPTION 'This field must be a valid percentage: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(
        name=u'integer_or_na_nd',
        or_replace=True,
        arguments=[
            {u'argname': u'value', u'argtype': u'text'},
            {u'argname': u'field_name', u'argtype': u'text'}],
        definition=u'''
            BEGIN
                IF value <> 'NA' AND value <> 'ND' AND NOT value ~ '^[0-9]+$' THEN
                    RAISE EXCEPTION 'This field must be NA or an integer: %', field_name;
                END IF;
            END;
        ''')

    service_choices = dict(
        (f['datastore_id'], f['choices'])
        for f in h.recombinant_choice_fields('service'))
    lc.action.datastore_function_create(
        name=u'service_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.service_id_number, 'service_id_number');
                PERFORM no_surrounding_whitespace(NEW.service_id_number, 'service_id_number');
                PERFORM not_empty(NEW.service_name_en, 'service_name_en');
                PERFORM not_empty(NEW.service_name_fr, 'service_name_fr');
                PERFORM not_empty(NEW.external_internal, 'external_internal');
                PERFORM choice_one_of(NEW.external_internal, {external_internal}, 'external_internal');
                PERFORM not_empty(NEW.service_type, 'service_type');
                PERFORM choice_one_of(NEW.service_type, {service_type}, 'service_type');
                PERFORM not_empty(NEW.special_designations, 'special_designations');
                PERFORM choice_one_of(NEW.special_designations, {special_designations}, 'special_designations');
                PERFORM not_empty(NEW.service_description_en, 'service_description_en');
                PERFORM not_empty(NEW.service_description_fr, 'service_description_fr');
                PERFORM not_empty(NEW.responsibility_area_en, 'responsibility_area_en');
                PERFORM not_empty(NEW.responsibility_area_fr, 'responsibility_area_fr');
                PERFORM not_empty(NEW.authority_en, 'authority_en');
                PERFORM not_empty(NEW.authority_fr, 'authority_fr');
                PERFORM not_empty(NEW.program_name_en, 'program_name_en');
                PERFORM not_empty(NEW.program_name_fr, 'program_name_fr');
                PERFORM not_empty(NEW.program_id_number, 'program_id_number');
                PERFORM not_empty(NEW.service_owner, 'service_owner');
                PERFORM choice_one_of(NEW.service_owner, {service_owner}, 'service_owner');
                PERFORM not_empty(NEW.service_agreements, 'service_agreements');
                PERFORM choice_one_of(NEW.service_agreements, {service_agreements}, 'service_agreements');
                PERFORM not_empty(NEW.client_target_groups, 'client_target_groups');
                NEW.client_target_groups := choices_from(
                    NEW.client_target_groups, {client_target_groups}, 'client_target_groups');
                PERFORM not_empty(NEW.cra_business_number, 'cra_business_number');
                PERFORM choice_one_of(NEW.cra_business_number, {cra_business_number}, 'cra_business_number');
                PERFORM not_empty(NEW.volumes_per_channel_online, 'volumes_per_channel_online');
                PERFORM integer_or_na_nd(NEW.volumes_per_channel_online, 'volumes_per_channel_online');
                PERFORM not_empty(NEW.volumes_per_channel_telephone, 'volumes_per_channel_telephone');
                PERFORM integer_or_na_nd(NEW.volumes_per_channel_telephone, 'volumes_per_channel_telephone');
                PERFORM not_empty(NEW.volumes_per_channel_in_person, 'volumes_per_channel_in_person');
                PERFORM integer_or_na_nd(NEW.volumes_per_channel_in_person, 'volumes_per_channel_in_person');
                PERFORM not_empty(NEW.volumes_per_channel_mail, 'volumes_per_channel_mail');
                PERFORM integer_or_na_nd(NEW.volumes_per_channel_mail, 'volumes_per_channel_mail');
                PERFORM not_empty(NEW.user_fee, 'user_fee');
                PERFORM choice_one_of(NEW.user_fee, {user_fee}, 'user_fee');
                PERFORM not_empty(NEW.targets_published_en, 'targets_published_en');
                PERFORM not_empty(NEW.targets_published_fr, 'targets_published_fr');
                PERFORM not_empty(NEW.e_registration, 'e_registration');
                PERFORM choice_one_of(NEW.e_registration, {e_registration}, 'e_registration');
                PERFORM not_empty(NEW.e_authentication, 'e_authentication');
                PERFORM choice_one_of(NEW.e_authentication, {e_authentication}, 'e_authentication');
                PERFORM not_empty(NEW.e_application, 'e_application');
                PERFORM choice_one_of(NEW.e_application, {e_application}, 'e_application');
                PERFORM not_empty(NEW.e_decision, 'e_decision');
                PERFORM choice_one_of(NEW.e_decision, {e_decision}, 'e_decision');
                PERFORM not_empty(NEW.e_issuance, 'e_issuance');
                PERFORM choice_one_of(NEW.e_issuance, {e_issuance}, 'e_issuance');
                PERFORM not_empty(NEW.e_feedback, 'e_feedback');
                PERFORM choice_one_of(NEW.e_feedback, {e_feedback}, 'e_feedback');
                PERFORM not_empty(NEW.interaction_points_online, 'interaction_points_online');
                PERFORM choice_one_of(NEW.interaction_points_online, {interaction_points_online}, 'interaction_points_online');
                PERFORM not_empty(NEW.interaction_points_total, 'interaction_points_total');
                PERFORM choice_one_of(NEW.interaction_points_total, {interaction_points_total}, 'interaction_points_total');
                PERFORM not_empty(NEW.percentage_online, 'percentage_online');
                PERFORM valid_percentage(NEW.percentage_online, 'percentage_online');
                RETURN NEW;
            END;
            '''.format(
                external_internal=pg_array(service_choices['external_internal']),
                service_type=pg_array(service_choices['service_type']),
                special_designations=pg_array(service_choices['special_designations']),
                service_owner=pg_array(service_choices['service_owner']),
                service_agreements=pg_array(service_choices['service_agreements']),
                client_target_groups=pg_array(service_choices['client_target_groups']),
                cra_business_number=pg_array(service_choices['cra_business_number']),
                user_fee=pg_array(service_choices['user_fee']),
                e_registration=pg_array(service_choices['e_registration']),
                e_authentication=pg_array(service_choices['e_authentication']),
                e_application=pg_array(service_choices['e_application']),
                e_decision=pg_array(service_choices['e_decision']),
                e_issuance=pg_array(service_choices['e_issuance']),
                e_feedback=pg_array(service_choices['e_feedback']),
                interaction_points_online=pg_array(service_choices['interaction_points_online']),
                interaction_points_total=pg_array(service_choices['interaction_points_total']),
            )
        )

    grants_choices = dict(
        (f['datastore_id'], f['choices'])
        for f in h.recombinant_choice_fields('grants'))
    lc.action.datastore_function_create(
        name=u'grants_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.ref_number, 'ref_number');

                PERFORM not_empty(NEW.amendment_number, 'amendment_number');
                IF NEW.amendment_number <> 0 THEN
                    PERFORM not_empty(NEW.amendment_date, 'amendment_date');
                END IF;

                IF NOT ((NEW.foreign_currency_type = '') IS NOT FALSE) OR
                        NEW.foreign_currency_value IS NOT NULL THEN
                    PERFORM not_empty(NEW.foreign_currency_type, 'foreign_currency_type');
                    PERFORM choice_one_of(
                        NEW.foreign_currency_type,
                        {foreign_currency_type},
                        'foreign_currency_type');
                    PERFORM not_empty(NEW.foreign_currency_value, 'foreign_currency_value');
                END IF;

                PERFORM not_empty(NEW.agreement_value, 'agreement_value');

                PERFORM not_empty(NEW.agreement_start_date, 'agreement_start_date');
                IF NEW.agreement_start_date >= '2018-04-01'::date THEN
                    PERFORM not_empty(NEW.agreement_type, 'agreement_type');
                    PERFORM choice_one_of(
                        NEW.agreement_type,
                        {agreement_type},
                        'agreement_type');
                    PERFORM choice_one_of(
                        NEW.recipient_type,
                        {recipient_type},
                        'recipient_type');
                    PERFORM not_empty(NEW.recipient_legal_name, 'recipient_legal_name');
                    PERFORM not_empty(NEW.recipient_country, 'recipient_country');
                    PERFORM choice_one_of(
                        NEW.recipient_country,
                        {recipient_country},
                        'recipient_country');
                    IF NEW.recipient_country = 'CA' THEN
                        PERFORM not_empty(NEW.recipient_province, 'recipient_province');
                    END IF;
                    PERFORM choice_one_of(
                        NEW.recipient_province,
                        {recipient_province},
                        'recipient_province');
                    PERFORM not_empty(NEW.recipient_city, 'recipient_city');
                    PERFORM not_empty(NEW.description_en, 'description_en');
                    PERFORM not_empty(NEW.description_fr, 'description_fr');
                END IF;
                RETURN NEW;
            END;
            '''.format(
                agreement_type=pg_array(grants_choices['agreement_type']),
                recipient_type=pg_array(grants_choices['recipient_type']),
                recipient_country=pg_array(grants_choices['recipient_country']),
                recipient_province=pg_array(grants_choices['recipient_province']),
                foreign_currency_type=pg_array(grants_choices['foreign_currency_type']),
            )
        )

    grants_nil_choices = dict(
        (f['datastore_id'], f['choices'])
        for f in h.recombinant_choice_fields('grants-nil'))
    lc.action.datastore_function_create(
        name=u'grants_nil_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.fiscal_year, 'fiscal_year');
                PERFORM choice_one_of(NEW.fiscal_year, {fiscal_year}, 'fiscal_year');
                PERFORM not_empty(NEW.quarter, 'quarter');
                PERFORM choice_one_of(NEW.quarter, {quarter}, 'quarter');
                RETURN NEW;
            END;
            '''.format(
                fiscal_year=pg_array(grants_nil_choices['fiscal_year']),
                quarter=pg_array(grants_nil_choices['quarter']),
            )
        )
Exemple #7
0
def update_triggers():
    """Create/update triggers used by PD tables"""

    lc = LocalCKAN()

    lc.action.datastore_function_create(name=u'not_empty',
                                        or_replace=True,
                                        arguments=[{
                                            u'argname': u'value',
                                            u'argtype': u'text'
                                        }, {
                                            u'argname': u'field_name',
                                            u'argtype': u'text'
                                        }],
                                        definition=u'''
            BEGIN
                IF (value = '') IS NOT FALSE THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(name=u'not_empty',
                                        or_replace=True,
                                        arguments=[{
                                            u'argname': u'value',
                                            u'argtype': u'date'
                                        }, {
                                            u'argname': u'field_name',
                                            u'argtype': u'text'
                                        }],
                                        definition=u'''
            BEGIN
                IF value IS NULL THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(name=u'not_empty',
                                        or_replace=True,
                                        arguments=[{
                                            u'argname': u'value',
                                            u'argtype': u'_text'
                                        }, {
                                            u'argname': u'field_name',
                                            u'argtype': u'text'
                                        }],
                                        definition=u'''
            BEGIN
                IF value = '{}' THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(name=u'not_empty',
                                        or_replace=True,
                                        arguments=[{
                                            u'argname': u'value',
                                            u'argtype': u'int4'
                                        }, {
                                            u'argname': u'field_name',
                                            u'argtype': u'text'
                                        }],
                                        definition=u'''
            BEGIN
                IF value IS NULL THEN
                    RAISE EXCEPTION 'This field must not be empty: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(name=u'no_surrounding_whitespace',
                                        or_replace=True,
                                        arguments=[{
                                            u'argname': u'value',
                                            u'argtype': u'text'
                                        }, {
                                            u'argname': u'field_name',
                                            u'argtype': u'text'
                                        }],
                                        definition=ur'''
            BEGIN
                IF trim(both E'\t\n\x0b\x0c\r ' from value) <> value THEN
                    RAISE EXCEPTION 'This field must not have surrounding whitespace: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(name=u'year_optional_month_day',
                                        or_replace=True,
                                        arguments=[{
                                            u'argname': u'value',
                                            u'argtype': u'text'
                                        }, {
                                            u'argname': u'field_name',
                                            u'argtype': u'text'
                                        }],
                                        definition=u'''
            DECLARE
                ymd _text := regexp_matches(value,
                    '(\d\d\d\d)(?:-(\d\d)(?:-(\d\d))?)?');
            BEGIN
                IF ymd IS NULL THEN
                    RAISE EXCEPTION 'Dates must be in YYYY-MM-DD format: %', field_name;
                END IF;
                IF ymd[3] IS NOT NULL THEN
                    PERFORM value::date;
                ELSIF NOT ymd[2]::int BETWEEN 1 AND 12 THEN
                    RAISE EXCEPTION 'Dates must be in YYYY-MM-DD format: %', field_name;
                END IF;
            EXCEPTION
                WHEN others THEN
                    RAISE EXCEPTION 'Dates must be in YYYY-MM-DD format: %', field_name;
            END;
        ''')

    lc.action.datastore_function_create(name=u'choice_one_of',
                                        or_replace=True,
                                        arguments=[{
                                            u'argname': u'value',
                                            u'argtype': u'text'
                                        }, {
                                            u'argname': u'choices',
                                            u'argtype': u'_text'
                                        }, {
                                            u'argname': u'field_name',
                                            u'argtype': u'text'
                                        }],
                                        definition=u'''
            BEGIN
                IF NOT (value = ANY (choices)) THEN
                    RAISE EXCEPTION 'Invalid choice for %: "%"', field_name, value;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(name=u'choices_from',
                                        or_replace=True,
                                        arguments=[{
                                            u'argname': u'value',
                                            u'argtype': u'_text'
                                        }, {
                                            u'argname': u'choices',
                                            u'argtype': u'_text'
                                        }, {
                                            u'argname': u'field_name',
                                            u'argtype': u'text'
                                        }],
                                        rettype=u'_text',
                                        definition=u'''
            DECLARE
                bad_choices text := array_to_string(ARRAY(
                    SELECT unnest(value)
                    EXCEPT SELECT unnest(choices)), ', ');
            BEGIN
                IF bad_choices <> '' THEN
                    RAISE EXCEPTION 'Invalid choice for %: "%"', field_name, bad_choices;
                END IF;
                RETURN ARRAY(
                    SELECT c FROM(SELECT unnest(choices) as c) u
                    WHERE c in (SELECT unnest(value)));
            END;
        ''')

    consultations_choices = dict(
        (f['datastore_id'], f['choices'])
        for f in h.recombinant_choice_fields('consultations'))
    lc.action.datastore_function_create(
        name=u'consultations_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.registration_number, 'registration_number');
                PERFORM choice_one_of(NEW.publishable, {publishable}, 'publishable');
                NEW.partner_departments := choices_from(
                    NEW.partner_departments, {partner_departments}, 'partner_departments');
                PERFORM choice_one_of(NEW.sector, {sectors}, 'sector');
                PERFORM not_empty(NEW.subjects, 'subjects');
                NEW.subjects := choices_from(
                    NEW.subjects, {subjects}, 'subjects');
                PERFORM not_empty(NEW.title_en, 'title_en');
                PERFORM not_empty(NEW.title_fr, 'title_fr');
                PERFORM not_empty(NEW.goals, 'goals');
                NEW.goals := choices_from(NEW.goals, {goals}, 'goals');
                PERFORM not_empty(NEW.description_en, 'description_en');
                PERFORM not_empty(NEW.description_fr, 'description_fr');
                PERFORM choice_one_of(
                    NEW.public_opinion_research,
                    {public_opinion_research},
                    'public_opinion_research');
                PERFORM choice_one_of(
                    NEW.public_opinion_research_standing_offer,
                    {public_opinion_research_standing_offer},
                    'public_opinion_research_standing_offer');
                PERFORM not_empty(
                    NEW.target_participants_and_audience,
                    'target_participants_and_audience');
                NEW.target_participants_and_audience := choices_from(
                    NEW.target_participants_and_audience,
                    {target_participants_and_audience},
                    'target_participants_and_audience');
                PERFORM not_empty(NEW.planned_start_date, 'planned_start_date');
                PERFORM not_empty(NEW.planned_end_date, 'planned_end_date');
                PERFORM choice_one_of(NEW.status, {status}, 'status');
                PERFORM not_empty(NEW.further_information_en, 'further_information_en');
                PERFORM not_empty(NEW.further_information_fr, 'further_information_fr');
                PERFORM choice_one_of(
                    NEW.report_available_online,
                    {report_available_online},
                    'report_available_online');
                PERFORM not_empty(NEW.rationale, 'rationale');
                NEW.rationale := choices_from(
                    NEW.rationale, {rationale}, 'rationale');

                RETURN NEW;
            END;
            '''.format(
            sectors=pg_array(consultations_choices['sector']),
            publishable=pg_array(consultations_choices['publishable']),
            partner_departments=pg_array(
                consultations_choices['partner_departments']),
            subjects=pg_array(consultations_choices['subjects']),
            goals=pg_array(consultations_choices['goals']),
            target_participants_and_audience=pg_array(
                consultations_choices['target_participants_and_audience']),
            public_opinion_research=pg_array(
                consultations_choices['public_opinion_research']),
            public_opinion_research_standing_offer=pg_array(
                consultations_choices['public_opinion_research_standing_offer']
            ),
            status=pg_array(consultations_choices['status']),
            report_available_online=pg_array(
                consultations_choices['report_available_online']),
            rationale=pg_array(consultations_choices['rationale']),
        ))
    lc.action.datastore_function_create(
        name=u'update_record_modified_created_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            DECLARE
                req_user_modified text := NEW.user_modified;
                username text NOT NULL := (SELECT username
                    FROM datastore_user);
                sysadmin boolean NOT NULL := (SELECT sysadmin
                    FROM datastore_user);
            BEGIN
                IF NOT sysadmin OR (req_user_modified = '') IS NOT FALSE THEN
                    req_user_modified := NULL;
                END IF;
                IF TG_OP = 'INSERT' THEN
                    IF NEW.record_created IS NULL THEN
                        NEW.record_created := now() at time zone 'utc';
                    END IF;
                    IF NEW.record_modified IS NULL THEN
                        NEW.record_modified := NEW.record_created;
                    END IF;
                    IF req_user_modified IS NULL THEN
                        NEW.user_modified := username;
                    END IF;
                    RETURN NEW;
                END IF;

                IF NEW.record_created IS NULL THEN
                    NEW.record_created := OLD.record_created;
                END IF;
                IF NEW.record_modified IS NULL THEN
                    NEW.record_modified := OLD.record_modified;
                END IF;
                IF req_user_modified IS NULL THEN
                    NEW.user_modified := OLD.user_modified;
                END IF;
                IF OLD = NEW THEN
                    RETURN NULL;
                END IF;
                NEW.record_modified := now() at time zone 'utc';
                IF req_user_modified IS NULL THEN
                    NEW.user_modified := username;
                ELSE
                    NEW.user_modified := req_user_modified;
                END IF;
                RETURN NEW;
            END;
            ''')

    inventory_choices = dict((f['datastore_id'], f['choices'])
                             for f in h.recombinant_choice_fields('inventory'))
    lc.action.datastore_function_create(
        name=u'inventory_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.ref_number, 'ref_number');
                PERFORM no_surrounding_whitespace(NEW.ref_number, 'ref_number');
                PERFORM not_empty(NEW.title_en, 'title_en');
                PERFORM not_empty(NEW.title_fr, 'title_fr');
                -- PERFORM not_empty(NEW.description_en, 'description_en');
                -- PERFORM not_empty(NEW.description_fr, 'description_fr');
                -- PERFORM not_empty(NEW.date_published, 'date_published');
                -- PERFORM year_optional_month_day(NEW.date_published, 'date_published');
                -- PERFORM not_empty(NEW.language, 'language');
                -- PERFORM choice_one_of(NEW.language, {language}, 'language');
                -- PERFORM not_empty(NEW.size, 'size');
                NEW.eligible_for_release := truthy_to_yn(NEW.eligible_for_release);
                -- PERFORM not_empty(NEW.eligible_for_release, 'eligible_for_release');
                -- PERFORM choice_one_of(NEW.eligible_for_release, {eligible_for_release}, 'eligible_for_release');
                -- PERFORM not_empty(NEW.program_alignment_architecture_en, 'program_alignment_architecture_en');
                -- PERFORM not_empty(NEW.program_alignment_architecture_fr, 'program_alignment_architecture_fr');
                -- PERFORM not_empty(NEW.date_released, 'date_released');
                -- PERFORM year_optional_month_day(NEW.date_released, 'date_released');
                RETURN NEW;
            END;
            '''.format(
            language=pg_array(inventory_choices['language']),
            eligible_for_release=pg_array(
                inventory_choices['eligible_for_release']),
        ))

    lc.action.datastore_function_create(name=u'protect_user_votes_trigger',
                                        or_replace=True,
                                        rettype=u'trigger',
                                        definition=u'''
            DECLARE
                req_user_votes int := NEW.user_votes;
                sysadmin boolean NOT NULL := (SELECT sysadmin
                    FROM datastore_user);
            BEGIN
                IF NOT sysadmin THEN
                    req_user_votes := NULL;
                END IF;

                IF req_user_votes IS NULL AND TG_OP = 'UPDATE' THEN
                    NEW.user_votes := OLD.user_votes;
                ELSE
                    NEW.user_votes = req_user_votes;
                END IF;

                IF NEW.user_votes IS NULL THEN
                    NEW.user_votes := 0;
                END IF;
                RETURN NEW;
            END;
            ''')

    lc.action.datastore_function_create(name=u'truthy_to_yn',
                                        or_replace=True,
                                        arguments=[{
                                            u'argname': u'value',
                                            u'argtype': u'text'
                                        }],
                                        rettype=u'text',
                                        definition=u'''
            DECLARE
                truthy boolean := value ~*
                    '[[:<:]](true|t|vrai|v|1|yes|y|oui|o)[[:>:]]';
                falsy boolean := value ~*
                    '[[:<:]](false|f|faux|0|no|n|non)[[:>:]]';
            BEGIN
                IF truthy AND NOT falsy THEN
                    RETURN 'Y';
                ELSIF falsy AND NOT truthy THEN
                    RETURN 'N';
                ELSE
                    RETURN NULL;
                END IF;
            END;
            ''')

    lc.action.datastore_function_create(name=u'contracts_trigger',
                                        or_replace=True,
                                        rettype=u'trigger',
                                        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.reference_number, 'reference_number');
                NEW.aboriginal_business := truthy_to_yn(NEW.aboriginal_business);
                NEW.potential_commercial_exploitation := truthy_to_yn(NEW.potential_commercial_exploitation);
                NEW.former_public_servant := truthy_to_yn(NEW.former_public_servant);
                RETURN NEW;
            END;
            ''')

    lc.action.datastore_function_create(name=u'valid_percentage',
                                        or_replace=True,
                                        arguments=[{
                                            u'argname': u'value',
                                            u'argtype': u'int4'
                                        }, {
                                            u'argname': u'field_name',
                                            u'argtype': u'text'
                                        }],
                                        definition=u'''
            BEGIN
                IF value < 0 OR value > 100 THEN
                    RAISE EXCEPTION 'This field must be a valid percentage: %', field_name;
                END IF;
            END;
        ''')

    lc.action.datastore_function_create(name=u'integer_or_na_nd',
                                        or_replace=True,
                                        arguments=[{
                                            u'argname': u'value',
                                            u'argtype': u'text'
                                        }, {
                                            u'argname': u'field_name',
                                            u'argtype': u'text'
                                        }],
                                        definition=u'''
            BEGIN
                IF value <> 'NA' AND value <> 'ND' AND NOT value ~ '^[0-9]+$' THEN
                    RAISE EXCEPTION 'This field must be NA or an integer: %', field_name;
                END IF;
            END;
        ''')

    service_choices = dict((f['datastore_id'], f['choices'])
                           for f in h.recombinant_choice_fields('service'))
    lc.action.datastore_function_create(
        name=u'service_trigger',
        or_replace=True,
        rettype=u'trigger',
        definition=u'''
            BEGIN
                PERFORM not_empty(NEW.service_id_number, 'service_id_number');
                PERFORM no_surrounding_whitespace(NEW.service_id_number, 'service_id_number');
                PERFORM not_empty(NEW.service_name_en, 'service_name_en');
                PERFORM not_empty(NEW.service_name_fr, 'service_name_fr');
                PERFORM not_empty(NEW.external_internal, 'external_internal');
                PERFORM choice_one_of(NEW.external_internal, {external_internal}, 'external_internal');
                PERFORM not_empty(NEW.service_type, 'service_type');
                PERFORM choice_one_of(NEW.service_type, {service_type}, 'service_type');
                PERFORM not_empty(NEW.special_designations, 'special_designations');
                PERFORM choice_one_of(NEW.special_designations, {special_designations}, 'special_designations');
                PERFORM not_empty(NEW.service_description_en, 'service_description_en');
                PERFORM not_empty(NEW.service_description_fr, 'service_description_fr');
                PERFORM not_empty(NEW.responsibility_area_en, 'responsibility_area_en');
                PERFORM not_empty(NEW.responsibility_area_fr, 'responsibility_area_fr');
                PERFORM not_empty(NEW.authority_en, 'authority_en');
                PERFORM not_empty(NEW.authority_fr, 'authority_fr');
                PERFORM not_empty(NEW.program_name_en, 'program_name_en');
                PERFORM not_empty(NEW.program_name_fr, 'program_name_fr');
                PERFORM not_empty(NEW.program_id_number, 'program_id_number');
                PERFORM not_empty(NEW.service_owner, 'service_owner');
                PERFORM choice_one_of(NEW.service_owner, {service_owner}, 'service_owner');
                PERFORM not_empty(NEW.service_agreements, 'service_agreements');
                PERFORM choice_one_of(NEW.service_agreements, {service_agreements}, 'service_agreements');
                PERFORM not_empty(NEW.client_target_groups, 'client_target_groups');
                PERFORM choice_one_of(NEW.client_target_groups, {client_target_groups}, 'client_target_groups');
                PERFORM not_empty(NEW.cra_business_number, 'cra_business_number');
                PERFORM choice_one_of(NEW.cra_business_number, {cra_business_number}, 'cra_business_number');
                PERFORM not_empty(NEW.volumes_per_channel_online, 'volumes_per_channel_online');
                PERFORM integer_or_na_nd(NEW.volumes_per_channel_online, 'volumes_per_channel_online');
                PERFORM not_empty(NEW.volumes_per_channel_telephone, 'volumes_per_channel_telephone');
                PERFORM integer_or_na_nd(NEW.volumes_per_channel_telephone, 'volumes_per_channel_telephone');
                PERFORM not_empty(NEW.volumes_per_channel_in_person, 'volumes_per_channel_in_person');
                PERFORM integer_or_na_nd(NEW.volumes_per_channel_in_person, 'volumes_per_channel_in_person');
                PERFORM not_empty(NEW.volumes_per_channel_mail, 'volumes_per_channel_mail');
                PERFORM integer_or_na_nd(NEW.volumes_per_channel_mail, 'volumes_per_channel_mail');
                PERFORM not_empty(NEW.user_fee, 'user_fee');
                PERFORM choice_one_of(NEW.user_fee, {user_fee}, 'user_fee');
                PERFORM not_empty(NEW.targets_published_en, 'targets_published_en');
                PERFORM not_empty(NEW.targets_published_fr, 'targets_published_fr');
                PERFORM not_empty(NEW.e_registration, 'e_registration');
                PERFORM choice_one_of(NEW.e_registration, {e_registration}, 'e_registration');
                PERFORM not_empty(NEW.e_authentication, 'e_authentication');
                PERFORM choice_one_of(NEW.e_authentication, {e_authentication}, 'e_authentication');
                PERFORM not_empty(NEW.e_application, 'e_application');
                PERFORM choice_one_of(NEW.e_application, {e_application}, 'e_application');
                PERFORM not_empty(NEW.e_decision, 'e_decision');
                PERFORM choice_one_of(NEW.e_decision, {e_decision}, 'e_decision');
                PERFORM not_empty(NEW.e_issuance, 'e_issuance');
                PERFORM choice_one_of(NEW.e_issuance, {e_issuance}, 'e_issuance');
                PERFORM not_empty(NEW.e_feedback, 'e_feedback');
                PERFORM choice_one_of(NEW.e_feedback, {e_feedback}, 'e_feedback');
                PERFORM not_empty(NEW.interaction_points_online, 'interaction_points_online');
                PERFORM choice_one_of(NEW.interaction_points_online, {interaction_points_online}, 'interaction_points_online');
                PERFORM not_empty(NEW.interaction_points_total, 'interaction_points_total');
                PERFORM choice_one_of(NEW.interaction_points_total, {interaction_points_total}, 'interaction_points_total');
                PERFORM not_empty(NEW.percentage_online, 'percentage_online');
                PERFORM valid_percentage(NEW.percentage_online, 'percentage_online');
                RETURN NEW;
            END;
            '''.format(
            external_internal=pg_array(service_choices['external_internal']),
            service_type=pg_array(service_choices['service_type']),
            special_designations=pg_array(
                service_choices['special_designations']),
            service_owner=pg_array(service_choices['service_owner']),
            service_agreements=pg_array(service_choices['service_agreements']),
            client_target_groups=pg_array(
                service_choices['client_target_groups']),
            cra_business_number=pg_array(
                service_choices['cra_business_number']),
            user_fee=pg_array(service_choices['user_fee']),
            e_registration=pg_array(service_choices['e_registration']),
            e_authentication=pg_array(service_choices['e_authentication']),
            e_application=pg_array(service_choices['e_application']),
            e_decision=pg_array(service_choices['e_decision']),
            e_issuance=pg_array(service_choices['e_issuance']),
            e_feedback=pg_array(service_choices['e_feedback']),
            interaction_points_online=pg_array(
                service_choices['interaction_points_online']),
            interaction_points_total=pg_array(
                service_choices['interaction_points_total']),
        ))