Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,30 @@
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Column;

/** PostgreSQL 18 table-level NOT NULL constraint, optionally named and non-inheritable. */
/** PostgreSQL 18 column- or table-level NOT NULL constraint. */
public class NotNullConstraint extends NamedConstraint {
private Column column;
private boolean noInherit;
private boolean columnConstraint;

public NotNullConstraint() {
setType("NOT NULL");
}

/** Whether the target is implicit in the enclosing column definition. */
public boolean isColumnConstraint() {
return columnConstraint;
}

public void setColumnConstraint(boolean columnConstraint) {
this.columnConstraint = columnConstraint;
}

public NotNullConstraint withColumnConstraint(boolean columnConstraint) {
setColumnConstraint(columnConstraint);
return this;
}

public Column getColumn() {
return column;
}
Expand Down Expand Up @@ -61,12 +76,15 @@ public NotNullConstraint withConstraintAttributes(ConstraintAttributes attribute

@Override
public void appendTo(StringBuilder sql, Consumer<Expression> expressionPrinter) {
if (column == null) {
if (!columnConstraint && column == null) {
throw new IllegalStateException("NOT NULL requires a target column");
}
appendConstraintPrefixTo(sql);
sql.append("NOT NULL ");
expressionPrinter.accept(column);
sql.append("NOT NULL");
if (!columnConstraint) {
sql.append(' ');
expressionPrinter.accept(column);
}
if (noInherit) {
sql.append(" NO INHERIT");
}
Expand Down
26 changes: 23 additions & 3 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -14119,6 +14119,11 @@ ColumnOption ColumnDefinitionOption(): {
|| (getToken(1).kind == K_AS && "(".equals(getToken(2).image)) })
generated=GeneratedColumnDefinition() { option = ColumnOption.generated(generated); }
|
LOOKAHEAD([ <K_CONSTRAINT> RelObjectName() ] <K_NOT> <K_NULL>,
{ Dialect.POSTGRESQL.name().equals(getAsString(Feature.dialect))
&& (getToken(1).kind == K_CONSTRAINT || getToken(3).kind == K_NO) })
constraint=PostgreSqlColumnNotNullConstraint() { option = ColumnOption.constraint(constraint); }
|
LOOKAHEAD(<K_NOT> <K_NULL>) <K_NOT> <K_NULL> { option = ColumnOption.nullability(false); }
|
LOOKAHEAD(<K_NULL>) <K_NULL> { option = ColumnOption.nullability(true); }
Expand Down Expand Up @@ -14892,14 +14897,29 @@ Index TableConstraint(boolean createContext):

/** PostgreSQL 18 adds a table-level NOT NULL with a named target column. */
NotNullConstraint PostgreSqlNotNullConstraint():
{ NotNullConstraint constraint = new NotNullConstraint(); String name; }
{ NotNullConstraint constraint = new NotNullConstraint(); String name; boolean noInherit; }
{
<K_NOT> <K_NULL> name=RelObjectName() { constraint.setColumn(createColumn(name)); }
[ LOOKAHEAD({ getToken(1).kind == K_NO && "INHERIT".equalsIgnoreCase(getToken(2).image) })
<K_NO> TypeDdlKeyword("INHERIT") { constraint.setNoInherit(true); } ]
noInherit=PostgreSqlNoInheritClause() { constraint.setNoInherit(noInherit); }
{ return constraint; }
}

NotNullConstraint PostgreSqlColumnNotNullConstraint():
{ String name = null; boolean noInherit; }
{
[ <K_CONSTRAINT> name=RelObjectName() ]
<K_NOT> <K_NULL> noInherit=PostgreSqlNoInheritClause()
{ return new NotNullConstraint().withColumnConstraint(true).withName(name).withNoInherit(noInherit); }
}

boolean PostgreSqlNoInheritClause():
{ boolean noInherit = false; }
{
[ LOOKAHEAD({ getToken(1).kind == K_NO && "INHERIT".equalsIgnoreCase(getToken(2).image) })
<K_NO> TypeDdlKeyword("INHERIT") { noInherit = true; } ]
{ return noInherit; }
}

ExcludeConstraint PostgreSqlExcludeConstraint():
{
ExcludeConstraint constraint = new ExcludeConstraint();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 JSQLParser
* %%
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
* #L%
*/
package net.sf.jsqlparser.statement.create;

import static org.junit.jupiter.api.Assertions.*;
import java.util.ArrayList;
import java.util.List;
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.alter.Alter;
import net.sf.jsqlparser.statement.create.table.ColumnDefinition;
import net.sf.jsqlparser.statement.create.table.ColumnOption;
import net.sf.jsqlparser.statement.create.table.CreateTable;
import net.sf.jsqlparser.statement.create.table.NotNullConstraint;
import net.sf.jsqlparser.util.TableDefinitionTraversal;
import net.sf.jsqlparser.util.deparser.StatementDeParser;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class PostgreSqlColumnNotNullTest {
@ParameterizedTest
@ValueSource(strings = {"NOT NULL NO INHERIT", "CONSTRAINT nn NOT NULL",
"CONSTRAINT nn NOT NULL NO INHERIT", "CONSTRAINT \"Named NN\" NOT NULL NO INHERIT"})
void reusesNotNullConstraintsInCreateAndAlterColumns(String clause) throws JSQLParserException {
for (String prefix : List.of("CREATE TABLE t (", "ALTER TABLE t ADD COLUMN ")) {
Statement statement = parse(prefix + "id INT " + clause + " DEFAULT 7"
+ (prefix.startsWith("CREATE") ? ", value INT)" : ", ADD COLUMN value INT"));
ColumnDefinition column = column(statement);
NotNullConstraint constraint = assertInstanceOf(NotNullConstraint.class,
column.getColumnOptions().get(0).getConstraint());
assertTrue(constraint.isColumnConstraint());
assertNull(constraint.getColumn());
assertEquals(clause.endsWith("NO INHERIT"), constraint.isNoInherit());
assertEquals(ColumnOption.Kind.DEFAULT, column.getColumnOptions().get(1).getKind());
roundTrip(statement);
constraint.setName("replacement");
constraint.setNoInherit(!constraint.isNoInherit());
assertTrue(statement.toString().contains("CONSTRAINT replacement NOT NULL"));
roundTrip(statement);
}
}

@Test
void retainsPlainNullabilityAndUsesTheExistingTableModel() throws JSQLParserException {
ColumnOption option =
column(parse("CREATE TABLE t (id INT NOT NULL)")).getColumnOptions().get(0);
assertEquals(ColumnOption.Kind.NULLABILITY, option.getKind());
assertEquals(false, option.getNullable());
NotNullConstraint table = (NotNullConstraint) ((CreateTable) parse(
"CREATE TABLE t (id INT, CONSTRAINT nn NOT NULL id NO INHERIT)")).getIndexes()
.get(0);
assertFalse(table.isColumnConstraint());
assertEquals("id", table.getColumn().getColumnName());
assertEquals("CONSTRAINT nn NOT NULL NO INHERIT", new NotNullConstraint()
.withColumnConstraint(true).withName("nn").withNoInherit(true).toString());
}

@Test
void doesNotInventASecondColumnExpressionForAnImplicitTarget() throws JSQLParserException {
CreateTable table =
(CreateTable) parse("CREATE TABLE t (id INT DEFAULT 7 NOT NULL NO INHERIT)");
List<Expression> expressions = new ArrayList<>();
TableDefinitionTraversal.visit(table, expressions::add, t -> {
});
assertEquals(1, expressions.size());
assertEquals("7", expressions.get(0).toString());
roundTrip(table);
assertEquals(2, CCJSqlParserUtil.parseStatements(table + "; SELECT 1",
p -> p.withDialect(Dialect.POSTGRESQL)).size());
}

private static ColumnDefinition column(Statement statement) {
return statement instanceof CreateTable
? ((CreateTable) statement).getColumnDefinitions().get(0)
: ((Alter) statement).getAlterExpressions().get(0).getColDataTypeList().get(0);
}

private static Statement parse(String sql) throws JSQLParserException {
return CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL));
}

private static void roundTrip(Statement statement) throws JSQLParserException {
StringBuilder out = new StringBuilder();
statement.accept(new StatementDeParser(out));
assertEquals(statement.toString(), out.toString());
assertEquals(out.toString(), parse(out.toString()).toString());
}
}
Loading