# 2025 September 18
#
# The author disclaims copyright to this source code.  In place of
# a legal notice, here is a blessing:
#
#    May you do good and not evil.
#    May you find forgiveness for yourself and forgive others.
#    May you share freely, never taking more than you give.
#
#*************************************************************************
#

set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix altercons

# If SQLITE_OMIT_ALTERTABLE is defined, omit this file.
ifcapable !altertable {
  finish_test
  return
}

foreach {tn before after} {
  1 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b)) }
    { CREATE TABLE t1(a, b) }

  2 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) NOT NULL) }
    { CREATE TABLE t1(a, b NOT NULL) }

  3 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b)NOT NULL) }
    { CREATE TABLE t1(a, b NOT NULL) }

  3 { CREATE TABLE t1(a, b NOT NULL CONSTRAINT abc CHECK(t1.a != t1.b)); }
    { CREATE TABLE t1(a, b NOT NULL) }

  4 { CREATE TABLE t1(a, b, CONSTRAINT abc CHECK(t1.a != t1.b)) }
    { CREATE TABLE t1(a, b) }

  5 { CREATE TABLE t1(a, b, CONSTRAINT abc CHECK(t1.a != t1.b), PRIMARY KEY(a))}
    { CREATE TABLE t1(a, b, PRIMARY KEY(a)) }

  6 { CREATE TABLE t1(a, b,CONSTRAINT abc CHECK(t1.a != t1.b),PRIMARY KEY(a))}
    { CREATE TABLE t1(a, b,PRIMARY KEY(a)) }

  7 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) CONSTRAINT def UNIQUE) }
    { CREATE TABLE t1(a, b CONSTRAINT def UNIQUE) }

  8 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) CHECK (123)) }
    { CREATE TABLE t1(a, b CHECK (123)) }

  9 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) DEFAULT NULL) }
    { CREATE TABLE t1(a, b DEFAULT NULL) }

 10 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) COLLATE nocase) }
    { CREATE TABLE t1(a, b COLLATE nocase) }

 11 { CREATE TABLE t1(a, b CONSTRAINT abc CHECK(t1.a != t1.b) REFERENCES t2) }
    { CREATE TABLE t1(a, b REFERENCES t2) }

 12 { CREATE TABLE t1(a, b, c, CONSTRAINT one CONSTRAINT abc CHECK(a!=b) CONSTRAINT three) }
    { CREATE TABLE t1(a, b, c, CONSTRAINT one CONSTRAINT three) }

 13 { CREATE TABLE t1(a, b, c, CONSTRAINT abc CONSTRAINT one CHECK(a!=b) CONSTRAINT three) }
    { CREATE TABLE t1(a, b, c, CONSTRAINT one CHECK(a!=b) CONSTRAINT three) }

 14 { CREATE TABLE t1(a, b, c, CONSTRAINT abc) }
    { CREATE TABLE t1(a, b, c) }

 15 { CREATE TABLE t1(a, b, c,      
                      CONSTRAINT abc, CHECK( a!=b )) }
    { CREATE TABLE t1(a, b, c, CHECK( a!=b )) }

 16 { CREATE TABLE t1(a, b, c, CONSTRAINT abc /* hello */ CHECK( a!=b )) }
    { CREATE TABLE t1(a, b, c) }

 17 { CREATE TABLE t1(a, b, c, /* world */ CONSTRAINT abc CHECK( a!=b )) }
    { CREATE TABLE t1(a, b, c) }

 18 { CREATE TABLE t1(a, b, c -- comment
  CONSTRAINT abc NOT NULL
  ) }
    { CREATE TABLE t1(a, b, c) }

 19 { CREATE TABLE t1(a, b, c, -- comment
  CONSTRAINT abc CHECK (a>b) CONSTRAINT two
  ) }
    { CREATE TABLE t1(a, b, c, CONSTRAINT two
  ) }

 20 { CREATE TABLE t1(a, b, c, CONSTRAINT one CONSTRAINT abc CHECK (a>b)CONSTRAINT two) }
    { CREATE TABLE t1(a, b, c, CONSTRAINT one CONSTRAINT two) }

 21 { CREATE TABLE t1(a, b, c CONSTRAINT abc AS (b+1)) }
    { CREATE TABLE t1(a, b, c AS (b+1)) }

 22 { CREATE TABLE t1(a, b, c CONSTRAINT abc GENERATED ALWAYS AS (b+1) STORED) }
    { CREATE TABLE t1(a, b, c GENERATED ALWAYS AS (b+1) STORED) }
} {
  reset_db

  do_execsql_test 1.$tn.0 $before

  do_execsql_test 1.$tn.1 {
    ALTER TABLE t1 DROP CONSTRAINT abc;
  } {}

  do_execsql_test 1.$tn.2 {
    SELECT sql FROM sqlite_schema WHERE name='t1'
  } [list [string trim $after]]
}

#-------------------------------------------------------------------------

do_execsql_test 2.0 {
  CREATE TABLE t2(x, y CONSTRAINT ccc UNIQUE);
}
do_catchsql_test 2.1 {
  ALTER TABLE t2 DROP CONSTRAINT ccc
} {1 {constraint may not be dropped: ccc}}
do_catchsql_test 2.2 {
  ALTER TABLE t2 DROP CONSTRAINT ddd
} {1 {no such constraint: ddd}}

#-------------------------------------------------------------------------
reset_db
foreach {tn col before after} {
  1 a { CREATE TABLE t1(a NOT NULL, b) }
      { CREATE TABLE t1(a, b) }

  2 a { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL, b) }
      { CREATE TABLE t1(a, b) }

  3 a { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL UNIQUE, b) }
      { CREATE TABLE t1(a UNIQUE, b) }

  4 b { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL UNIQUE, b) }
      { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL UNIQUE, b) }

  5 a { CREATE TABLE t1(a CHECK(a<b) NOT NULL, b) }
      { CREATE TABLE t1(a CHECK(a<b), b) }

  6 a { CREATE TABLE t1(a CHECK(a<b) CONSTRAINT nn NOT NULL, b) }
      { CREATE TABLE t1(a CHECK(a<b), b) }

  7 b { CREATE TABLE t1(a, b NOT NULL PRIMARY KEY) }
      { CREATE TABLE t1(a, b PRIMARY KEY) }

  8 b { CREATE TABLE t1(a, b CHECK ((b+a) IS NOT NULL) NOT NULL PRIMARY KEY) }
      { CREATE TABLE t1(a, b CHECK ((b+a) IS NOT NULL) PRIMARY KEY) }

  9 b { CREATE TABLE t1(a, b CONSTRAINT nn CHECK (b IS NOT NULL) NOT NULL) }
      { CREATE TABLE t1(a, b CONSTRAINT nn CHECK (b IS NOT NULL)) }

 10 b { CREATE TABLE t1(a, b NOT NULL AS (a+1)) }
      { CREATE TABLE t1(a, b AS (a+1)) }

 11 b { CREATE TABLE t1(a, b NOT NULL GENERATED ALWAYS AS (a+1)) }
      { CREATE TABLE t1(a, b GENERATED ALWAYS AS (a+1)) }
} {
  reset_db

  do_execsql_test 3.$tn.0 $before

  do_execsql_test 3.$tn.1 "
    ALTER TABLE t1 ALTER COLUMN $col DROP NOT NULL
  "

  do_execsql_test 3.$tn.2 {
    SELECT sql FROM sqlite_schema WHERE name='t1'
  } [list [string trim $after]]
}

#-------------------------------------------------------------------------
#
reset_db
do_execsql_test 4.0 {
  CREATE TABLE t2(x, y CONSTRAINT ccc UNIQUE);
}
do_execsql_test 4.1 {
  ALTER TABLE t2 ALTER x DROP NOT NULL;
  ALTER TABLE t2 ALTER x DROP NOT NULL;
  ALTER TABLE t2 ALTER x DROP NOT NULL;
} {}

#-------------------------------------------------------------------------
#
reset_db

do_execsql_test 5.1 {
  CREATE TABLE t3(a INTEGER PRIMARY KEY, b);
  INSERT INTO t3 VALUES(1000, NULL);
}

do_catchsql_test 5.2.1 {
  ALTER TABLE t3 ALTER b SET NOT NULL
} {1 {constraint failed}}

do_test 5.2.2 {
  sqlite3_errcode db
} {SQLITE_CONSTRAINT}

foreach {tn before alter after} {
  1  { CREATE TABLE t1(a, b) }
     { ALTER TABLE t1 ALTER a SET NOT NULL }
     { CREATE TABLE t1(a NOT NULL, b) }

  2  { CREATE TABLE t1(a, b) }
     { ALTER TABLE t1 ALTER a SET NOT NULL ON CONFLICT FAIL }
     { CREATE TABLE t1(a NOT NULL ON CONFLICT FAIL, b) }

  3  { CREATE TABLE t1(a, b) }
     { ALTER TABLE t1 ALTER a SET NOT NULL ON CONFLICT fail; }
     { CREATE TABLE t1(a NOT NULL ON CONFLICT fail, b) }

  4  { CREATE TABLE t1(a, b) }
     { ALTER TABLE t1 ALTER b SET NOT   NULL ON CONFLICT IGNORE ; }
     { CREATE TABLE t1(a, b NOT   NULL ON CONFLICT IGNORE) }

  5  { CREATE TABLE t1(a, 'a b c' VARCHAR(10), UNIQUE(a)) }
     { ALTER TABLE t1 ALTER 'a b c' SET NOT NULL }
     { CREATE TABLE t1(a, 'a b c' VARCHAR(10) NOT NULL, UNIQUE(a)) }
} {
  reset_db
  do_execsql_test 5.3.$tn.1 $before
  do_execsql_test 5.3.$tn.2 $alter
  do_execsql_test 5.3.$tn.3 {
    SELECT sql FROM sqlite_schema WHERE name='t1';
  } [list [string trim $after]]
}

do_execsql_test 5.4.1 {
  CREATE TABLE x1(a, b, c);
}
do_catchsql_test 5.4.2 {
  ALTER TABLE x1 ALTER d SET NOT NULL;
} {1 {no such column: d}}
do_catchsql_test 5.4.3 {
  ALTER TABLE x2 ALTER c SET NOT NULL;
} {1 {no such table: x2}}
do_catchsql_test 5.4.4 {
  ALTER TABLE temp.x1 ALTER c SET NOT NULL;
} {1 {no such table: temp.x1}}

#-------------------------------------------------------------------------
#
reset_db

do_execsql_test 6.1 {
  CREATE TABLE t1(a, b, c);
  INSERT INTO t1 VALUES(1, 2, 3);
  INSERT INTO t1 VALUES(4, 5, 6);
}

do_catchsql_test 6.2.1 {
  ALTER TABLE t1 ADD CONSTRAINT nn CHECK (c!=6);
} {1 {constraint failed}}
do_execsql_test 6.2.2 {
  DELETE FROM t1 WHERE c=6;
  ALTER TABLE t1 ADD CONSTRAINT nn CHECK (c!=6);
} {}
do_catchsql_test 6.2.3 {
  INSERT INTO t1 VALUES(4, 5, 6);
} {1 {CHECK constraint failed: nn}}

foreach {tn before alter after} {
  1 { CREATE TABLE t1(a, b) }
    { ALTER TABLE t1 ADD CONSTRAINT nn CHECK (a>=0) }
    { CREATE TABLE t1(a, b, CONSTRAINT nn CHECK (a>=0)) }

  2 { CREATE TABLE t1(a, b  ) }
    { ALTER TABLE t1 ADD CONSTRAINT nn CHECK (a>=0) }
    { CREATE TABLE t1(a, b  , CONSTRAINT nn CHECK (a>=0)) }

  3 { CREATE TABLE t1(a, b  ) }
    { ALTER TABLE t1 ADD CHECK (a>=0) }
    { CREATE TABLE t1(a, b  , CHECK (a>=0)) }
} {
  reset_db
  do_execsql_test 6.3.$tn.1 $before
  do_execsql_test 6.3.$tn.2 $alter
  do_execsql_test 6.3.$tn.3 {
    SELECT sql FROM sqlite_schema WHERE type='table';
  } [list [string trim $after]]
}

do_execsql_test 6.4.1 {
  CREATE TABLE b1(a, b, CONSTRAINT abc CHECK (a!=2));
}
do_catchsql_test 6.4.2 {
  ALTER TABLE b1 ADD CONSTRAINT abc CHECK (a!=3);
} {1 {constraint abc already exists}}
do_execsql_test 6.4.1 {
  SELECT sql FROM sqlite_schema WHERE tbl_name='b1'
} {{CREATE TABLE b1(a, b, CONSTRAINT abc CHECK (a!=2))}}

do_execsql_test 6.5 {
  CREATE TABLE abc(x,y);
}

do_catchsql_test 6.6 {
  ALTER TABLE abc ADD CHECK (z>=0);
} {1 {no such column: z}}

#-------------------------------------------------------------------------
# Try attaching a NOT NULL to a generated column.
#
reset_db
do_execsql_test 7.0 {
  CREATE TABLE x1(a, b AS (a+1));
  INSERT INTO x1 VALUES(1), (2), (3), (NULL);
}

do_catchsql_test 7.1 {
  ALTER TABLE x1 ALTER b SET NOT NULL;
} {1 {constraint failed}}

do_catchsql_test 7.2 {
  DELETE FROM x1 WHERE b IS NULL;
  ALTER TABLE x1 ALTER b SET NOT NULL;
} {0 {}}

do_execsql_test 7.3 {
  SELECT b FROM x1
} {2 3 4}

do_catchsql_test 7.4 {
  ALTER TABLE x1 ALTER rowid SET NOT NULL;
} {1 {no such column: rowid}}

do_execsql_test 7.5 {
  CREATE VIEW v1 AS SELECT a, b FROM x1;
}
do_catchsql_test 7.6 {
  ALTER TABLE v1 RENAME a TO c;
} {1 {cannot rename columns of view "v1"}}
do_catchsql_test 7.7 {
  ALTER TABLE v1 ALTER a SET NOT NULL;
} {1 {cannot edit constraints of view "v1"}}
do_catchsql_test 7.8 {
  ALTER TABLE sqlite_schema ALTER sql SET NOT NULL;
} {1 {table sqlite_master may not be altered}}
do_catchsql_test 7.9 {
  ALTER TABLE v1 ALTER a DROP NOT NULL
} {1 {cannot edit constraints of view "v1"}}

#-------------------------------------------------------------------------
reset_db
do_execsql_test 8.0 {
  CREATE TABLE t1(a INTEGER PRIMARY KEY, b NOT NULL, c CHECK (c!=555), d);
  INSERT INTO t1 VALUES(1, 1, 1, 1);
  INSERT INTO t1 VALUES(2, 2, 2, 2);
  INSERT INTO t1 VALUES(3, 3, 3, 3);
}

do_execsql_test 8.1.1 {
  ALTER TABLE t1 ALTER a SET NOT NULL;
  ALTER TABLE t1 ALTER b SET NOT NULL;
  ALTER TABLE t1 ALTER c SET NOT NULL;
  ALTER TABLE t1 ALTER d SET NOT NULL;
}

do_execsql_test 8.1.2 {
  SELECT sql FROM sqlite_schema WHERE tbl_name = 't1'
} {{CREATE TABLE t1(a INTEGER PRIMARY KEY NOT NULL, b NOT NULL, c CHECK (c!=555) NOT NULL, d NOT NULL)}}

do_execsql_test 8.1.3 {
  SELECT * FROM t1 WHERE a=2;
} {2 2 2 2}

do_execsql_test 8.2.1 {
  ALTER TABLE t1 ALTER a DROP NOT NULL;
  ALTER TABLE t1 ALTER b DROP NOT NULL;
  ALTER TABLE t1 ALTER c DROP NOT NULL;
  ALTER TABLE t1 ALTER d DROP NOT NULL;
}

do_execsql_test 8.2.2 {
  SELECT sql FROM sqlite_schema WHERE tbl_name = 't1'
} {{CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c CHECK (c!=555), d)}}

do_execsql_test 8.2.3 {
  SELECT * FROM t1 WHERE a=3;
} {3 3 3 3}

#-------------------------------------------------------------------------
reset_db
forcedelete test.db2
do_execsql_test 9.0 {
  CREATE TABLE t1(x, y, z);
  ATTACH 'test.db2' AS aux;
  CREATE TABLE aux.t1(x, y, z);
  INSERT INTO aux.t1 VALUES(1, 1, 1);
  INSERT INTO aux.t1 VALUES(2, 2, 2);
  INSERT INTO aux.t1 VALUES(3, 3, NULL);

  CREATE TABLE aux.t2(x, y, z);
}

do_catchsql_test 9.1.1 {
  ALTER TABLE aux.t1 ALTER COLUMN z SET NOT NULL
} {1 {constraint failed}}
do_execsql_test 9.1.2 {
  UPDATE aux.t1 SET z=x;
  ALTER TABLE aux.t1 ALTER COLUMN z SET NOT NULL;
  SELECT sql FROM aux.sqlite_schema WHERE name='t1';
} {{CREATE TABLE t1(x, y, z NOT NULL)}}
do_execsql_test 9.1.3 {
  ALTER TABLE aux.t1 ALTER z DROP NOT NULL;
  SELECT sql FROM aux.sqlite_schema WHERE name='t1';
} {{CREATE TABLE t1(x, y, z)}}
do_execsql_test 9.1.4 {
  ALTER TABLE t2 ALTER x SET NOT NULL;
  SELECT sql FROM aux.sqlite_schema WHERE name='t2';
} {{CREATE TABLE t2(x NOT NULL, y, z)}}
do_execsql_test 9.1.5 {
  ALTER TABLE t2 ALTER x DROP NOT NULL;
  SELECT sql FROM aux.sqlite_schema WHERE name='t2';
} {{CREATE TABLE t2(x, y, z)}}

do_catchsql_test 9.2.1 {
  ALTER TABLE aux.t1 ADD CONSTRAINT bill CHECK (y!=2);
} {1 {constraint failed}}
do_execsql_test 9.2.2 {
  UPDATE aux.t1 SET y=4 WHERE y=2;
  ALTER TABLE aux.t1 ADD CONSTRAINT bill CHECK (y!=2);
  SELECT sql FROM aux.sqlite_schema WHERE name='t1';
} {{CREATE TABLE t1(x, y, z, CONSTRAINT bill CHECK (y!=2))}}
do_execsql_test 9.2.3 {
  ALTER TABLE aux.t1 DROP CONSTRAINT bill;
  SELECT sql FROM aux.sqlite_schema WHERE name='t1';
} {{CREATE TABLE t1(x, y, z)}}
do_execsql_test 9.2.4 {
  ALTER TABLE t2 ADD CONSTRAINT william CHECK (z!='');
  SELECT sql FROM aux.sqlite_schema WHERE name='t2';
} {{CREATE TABLE t2(x, y, z, CONSTRAINT william CHECK (z!=''))}}
do_execsql_test 9.2.5 {
  ALTER TABLE t2 DROP CONSTRAINT william;
  SELECT sql FROM aux.sqlite_schema WHERE name='t2';
} {{CREATE TABLE t2(x, y, z)}}

# Cannot add constraints the use internal functions.
# Forum post 2026-05-10T01:11:28Z
#
reset_db
do_execsql_test 10.1 {
  CREATE TABLE t1(x,y,z);
  INSERT INTO t1 VALUES(1,'two',x'3333');
}
do_catchsql_test 10.2 {
  ALTER TABLE t1 ADD CONSTRAINT c1 CHECK( sqlite_drop_column(22,'CREATE TABLE a(b,c)', 0));
} {1 {no such function: sqlite_drop_column}}
do_catchsql_test 10.3 {
  ALTER TABLE t1 ADD CONSTRAINT c1 CHECK( x>#2 );
} {1 {near "#2": syntax error}}

finish_test
