Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

is_not_null always false when visiting ColumnDef #150

Closed
bolajiwahab opened this issue Jun 14, 2024 · 4 comments
Closed

is_not_null always false when visiting ColumnDef #150

bolajiwahab opened this issue Jun 14, 2024 · 4 comments

Comments

@bolajiwahab
Copy link

bolajiwahab commented Jun 14, 2024

Hi there,

First, thanks for this awesome project.

I am using visitors pattern to visit constraint node but I noticed is_not_null is always false irrespective of not null defined on the column. Is this the expectation or may it is a bug?

Example

CREATE TABLE measurement (updated_at timestamptz not null);
def visit_ColumnDef(
    self,
    ancestors: ast.Node,
    node: ast.ColumnDef,
) -> None:
    """Visit ColumnDef."""
    print(node)
    print(node.is_not_null)

output

<ColumnDef colname='updated_at' typeName=<TypeName names=(<String sval='timestamptz'>,) setof=False pct_type=False typemod=-1> inhcount=0 is_local=True is_not_null=False is_from_type=False storage='\x00' identity='\x00' generated='\x00' constraints=(<Constraint contype=<ConstrType.CONSTR_NOTNULL: 1> deferrable=False initdeferred=False is_no_inherit=False generated_when='\x00' nulls_not_distinct=False reset_default_tblspc=False fk_matchtype='\x00' fk_upd_action='\x00' fk_del_action='\x00' skip_validation=False initially_valid=False>,)>

False

This is the class representation

class pglast.ast.ColumnDef(colname=None, typeName=None, inhcount=None, is_local=None, is_not_null=None, is_from_type=None, storage=None, raw_default=None, cooked_default=None, identity=None, identitySequence=None, generated=None, collClause=None, constraints=None, fdwoptions=None, location=None)

Thanks.

@bolajiwahab bolajiwahab changed the title is_not_null always false for when visiting ColumnDef is_not_null always false when visiting ColumnDef Jun 14, 2024
@lelit
Copy link
Owner

lelit commented Jun 15, 2024

TL;DR: That's a good question, but I cannot say if it is indeed a bug or not, I will have to forward the question to the underlying libpgquery team.

In the meantime, this is my reasoning: the ColumnDef structure effectively contains a is_not_null flag, but apparently it is never used (neither set nor queried) by the underlying parser.

This is what pglast obtains from the parser for your statement:

$ pgpp -tS "CREATE TABLE measurement (updated_at timestamptz not null);"
[{'@': 'RawStmt',
  'stmt': {'@': 'CreateStmt',
           'if_not_exists': False,
           'oncommit': {'#': 'OnCommitAction',
                        'name': 'ONCOMMIT_NOOP',
                        'value': 0},
           'relation': {'@': 'RangeVar',
                        'inh': True,
                        'location': 13,
                        'relname': 'measurement',
                        'relpersistence': 'p'},
           'tableElts': ({'@': 'ColumnDef',
                          'colname': 'updated_at',
                          'constraints': ({'@': 'Constraint',
                                           'contype': {'#': 'ConstrType',
                                                       'name': 'CONSTR_NOTNULL',
                                                       'value': 1},
                                           'deferrable': False,
                                           'fk_del_action': '\x00',
                                           'fk_matchtype': '\x00',
                                           'fk_upd_action': '\x00',
                                           'generated_when': '\x00',
                                           'initdeferred': False,
                                           'initially_valid': False,
                                           'is_no_inherit': False,
                                           'location': 49,
                                           'nulls_not_distinct': False,
                                           'reset_default_tblspc': False,
                                           'skip_validation': False},),
                          'generated': '\x00',
                          'identity': '\x00',
                          'inhcount': 0,
                          'is_from_type': False,
                          'is_local': True,
                          'is_not_null': False,
                          'location': 26,
                          'storage': '\x00',
                          'typeName': {'@': 'TypeName',
                                       'location': 37,
                                       'names': ({'@': 'String',
                                                  'sval': 'timestamptz'},),
                                       'pct_type': False,
                                       'setof': False,
                                       'typemod': -1}},)},
  'stmt_len': 58,
  'stmt_location': 0}]

and this is what it gets removing that not null constraint:

$ pgpp -tS "CREATE TABLE measurement (updated_at timestamptz);"
[{'@': 'RawStmt',
  'stmt': {'@': 'CreateStmt',
           'if_not_exists': False,
           'oncommit': {'#': 'OnCommitAction',
                        'name': 'ONCOMMIT_NOOP',
                        'value': 0},
           'relation': {'@': 'RangeVar',
                        'inh': True,
                        'location': 13,
                        'relname': 'measurement',
                        'relpersistence': 'p'},
           'tableElts': ({'@': 'ColumnDef',
                          'colname': 'updated_at',
                          'generated': '\x00',
                          'identity': '\x00',
                          'inhcount': 0,
                          'is_from_type': False,
                          'is_local': True,
                          'is_not_null': False,
                          'location': 26,
                          'storage': '\x00',
                          'typeName': {'@': 'TypeName',
                                       'location': 37,
                                       'names': ({'@': 'String',
                                                  'sval': 'timestamptz'},),
                                       'pct_type': False,
                                       'setof': False,
                                       'typemod': -1}},)},
  'stmt_len': 49,
  'stmt_location': 0}]

as you can see, the main difference is the presence/absence of the CONSTR_NOTNULL constraint, but the is_not_null flag is always left to false.

Looking down in the pristine PostgreSQL sources, I see that that transformCreateStmt() function mentioned in the ColumnDef's docstring is indeed one of the few points that sets that flag: here it calls transformColumnDefinition() that in turn updates that flag analyzing all the column's constraints.

For some reason, libpgquery variant of the PG parser does not call that transformation: from the point of view of pglast this does not make a big difference, because what it needs to reprint the statement is all there, so those flags are probably "just" an optimization needed by some other PG layer.

As said, I will ask an opinion to the libpgquery team.

@lelit
Copy link
Owner

lelit commented Jun 25, 2024

As explained by Lukas, that flag is indeed set by a later analysis step done by PG after that parse of the statement.
For the sake of consistency pglast could mimic those further steps, but I sincerely doubt it's worth the effort. WDYT?

@bolajiwahab
Copy link
Author

bolajiwahab commented Jun 25, 2024

Hi @lelit, thanks for the feedback and the explanation. Currently, one could check if there exists not null constraint in the column definition and use that.

@lelit
Copy link
Owner

lelit commented Jun 26, 2024

Right.

@lelit lelit closed this as completed Jun 26, 2024
lelit added a commit that referenced this issue Aug 6, 2024
The question on issue #150 clarified that the is_not_null flag is not
set by libpg_query parser: instead of writing out something wrong, raise
an exception should that behaviour change in the future.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants