diff --git a/Qsi.Tests/Vendor/PostgreSql/Driver/PostgreSqlRepositoryProvider.cs b/Qsi.Tests/Vendor/PostgreSql/Driver/PostgreSqlRepositoryProvider.cs
index 4541cd2b..1dee064c 100644
--- a/Qsi.Tests/Vendor/PostgreSql/Driver/PostgreSqlRepositoryProvider.cs
+++ b/Qsi.Tests/Vendor/PostgreSql/Driver/PostgreSqlRepositoryProvider.cs
@@ -7,6 +7,7 @@
using Qsi.Data.Object;
using Qsi.Data.Object.Function;
using Qsi.Engines;
+using Qsi.PostgreSql.Data;
using Qsi.Utilities;
namespace Qsi.Tests.PostgreSql.Driver;
@@ -76,7 +77,7 @@ from information_schema.TABLES
}
sql = $@"
-select COLUMN_NAME
+select COLUMN_NAME, IS_NULLABLE, COLUMN_DEFAULT
from information_schema.COLUMNS
where TABLE_CATALOG = '{table.Identifier[0]}' and TABLE_SCHEMA = '{table.Identifier[1].Value}' and TABLE_NAME = '{table.Identifier[2].Value}'
order by ORDINAL_POSITION";
@@ -87,6 +88,8 @@ from information_schema.COLUMNS
{
var column = table.NewColumn();
column.Name = new QsiIdentifier(reader.GetString(0), false);
+ column.IsNullable = reader.GetString(1) == "YES";
+ column.Default = reader.IsDBNull(2) ? null : reader.GetString(2);
}
}
diff --git a/Qsi.Tests/Vendor/PostgreSql/PostgreSqlTest.cs b/Qsi.Tests/Vendor/PostgreSql/PostgreSqlTest.cs
index e2909558..7f3cd4e7 100644
--- a/Qsi.Tests/Vendor/PostgreSql/PostgreSqlTest.cs
+++ b/Qsi.Tests/Vendor/PostgreSql/PostgreSqlTest.cs
@@ -274,6 +274,38 @@ public async Task Test_DML(string query, string[] expectedQueries, int expectedR
Assert.Pass();
}
+ ///
+ /// Insert Action 시 Not Null 제약조건 있는 테이블에 값 대입하는 상황에 관한 에러 처리를 확인하는 테스트를 수행합니다.
+ ///
+ [Test]
+ public async Task Test_InsertNotNull()
+ {
+ const string CreateTableQuery = @"create table if not exists test_not_null (
+col1 VARCHAR NOT NULL,
+col2 VARCHAR
+)";
+
+ var command = Connection.CreateCommand();
+ command.CommandText = CreateTableQuery;
+ await command.ExecuteNonQueryAsync();
+
+ string[] queries =
+ {
+ "INSERT INTO test_not_null VALUES (null, 'test')",
+ "INSERT INTO test_not_null (col2) VALUES ('test')"
+ };
+
+ const string errorMessage = "QSI-0021: The column 'col1' has a Not Null constraint.";
+
+ foreach (var query in queries)
+ {
+ Assert.ThrowsAsync(async () =>
+ {
+ await Engine.Execute(new QsiScript(query, QsiScriptType.Insert), null);
+ }, errorMessage);
+ }
+ }
+
///
/// Parameterized query에 대하여 테스트를 수행합니다.
///
diff --git a/Qsi/Analyzers/Action/QsiActionAnalyzer.cs b/Qsi/Analyzers/Action/QsiActionAnalyzer.cs
index 0085dc47..5b7c2027 100644
--- a/Qsi/Analyzers/Action/QsiActionAnalyzer.cs
+++ b/Qsi/Analyzers/Action/QsiActionAnalyzer.cs
@@ -466,6 +466,10 @@ protected virtual async ValueTask ExecuteDataInsertAction(
}
ColumnTarget[] columnTargets = await ResolveColumnTargetsFromDataInsertActionAsync(context, table, action);
+ var columnWithInvalidDefault = ResolveNotNullableColumnWithInvalidDefault(table.Columns, columnTargets);
+
+ if (columnWithInvalidDefault is not null)
+ throw new QsiException(QsiError.NotNullConstraints, columnWithInvalidDefault.Name.Value);
var dataContext = new TableDataInsertContext(context, table)
{
@@ -617,6 +621,14 @@ protected virtual SetColumnTarget ResolveSetColumnTarget(
);
}
+ protected virtual QsiTableColumn ResolveNotNullableColumnWithInvalidDefault(IEnumerable columns, IEnumerable columnTargets)
+ {
+ HashSet targetNames = columnTargets.Select(ct => ct.DeclaredName.SubIdentifier(0).ToString()).ToHashSet();
+
+ return columns
+ .FirstOrDefault(x => !targetNames.Contains(x.Name.Value) && !x.IsNullable && x.Default is null);
+ }
+
private async ValueTask ProcessQueryValues(TableDataInsertContext context, IQsiTableDirectivesNode directives, IQsiTableNode valueTable)
{
var engine = context.Engine;
@@ -745,6 +757,9 @@ private void PopulateInsertRow(TableDataInsertContext context, DataValueSelector
item = pivot.SourceColumn is not null
? valueSelector(pivot)
: ResolveDefaultColumnValue(pivot);
+
+ if (item.Value is null && target.Table.Columns[pivot.DestinationOrder].IsNullable == false)
+ throw new QsiException(QsiError.NotNullConstraints, pivot.DestinationColumn.Name.Value);
}
target.InsertRows.Add(targetRow);
diff --git a/Qsi/Data/Table/QsiTableColumn.cs b/Qsi/Data/Table/QsiTableColumn.cs
index 24fe30bb..398610fb 100644
--- a/Qsi/Data/Table/QsiTableColumn.cs
+++ b/Qsi/Data/Table/QsiTableColumn.cs
@@ -35,6 +35,8 @@ public bool IsExpression
set => _isExpression = value;
}
+ public bool IsNullable { get; set; } = true;
+
internal QsiQualifiedIdentifier ImplicitTableWildcardTarget { get; set; }
internal bool _isExpression;
@@ -52,6 +54,7 @@ internal QsiTableColumn CloneInternal()
column.ImplicitTableWildcardTarget = ImplicitTableWildcardTarget;
column._isExpression = _isExpression;
column.HasIndex = HasIndex;
+ column.IsNullable = IsNullable;
return column;
}
diff --git a/Qsi/QsiError.cs b/Qsi/QsiError.cs
index a8a6b7fd..62984b14 100644
--- a/Qsi/QsiError.cs
+++ b/Qsi/QsiError.cs
@@ -34,7 +34,8 @@ public enum QsiError
ParameterNotFound,
InvalidNestedExplain,
SubqueryReturnsMoreThanRow,
- UnableResolveFunction
+ UnableResolveFunction,
+ NotNullConstraints
}
internal static class SR
@@ -72,6 +73,7 @@ internal static class SR
public const string InvalidNestedExplain = "Invalid nested explain for '{0}'";
public const string SubqueryReturnsMoreThanRow = "Subquery returns more than {0} row";
public const string UnableResolveFunction = "Unable to resolve function '{0}'";
+ public const string NotNullConstraints = "The column '{0}' has a Not Null constraint.";
public static string GetResource(QsiError error)
{
@@ -110,6 +112,7 @@ public static string GetResource(QsiError error)
QsiError.InvalidNestedExplain => InvalidNestedExplain,
QsiError.SubqueryReturnsMoreThanRow => SubqueryReturnsMoreThanRow,
QsiError.UnableResolveFunction => UnableResolveFunction,
+ QsiError.NotNullConstraints => NotNullConstraints,
_ => null
};
}