From 075272a71c1d0574f49f573e8bd9e3d1ec3b811a Mon Sep 17 00:00:00 2001 From: hfhbd Date: Fri, 4 Aug 2023 11:25:22 +0200 Subject: [PATCH 1/4] Add column helper --- .../psi/core/psi/mixins/ColumnNameMixin.kt | 18 ++++++ .../psi/core/psi/mixins/CreateTableMixin.kt | 7 ++- .../core/psi/mixins/ForeignKeyClauseMixin.kt | 26 +++++++++ .../com/alecstrong/sql/psi/core/sql.bnf | 3 +- .../sql/psi/core/GetForeignKeyHelperTest.kt | 57 +++++++++++++++++++ 5 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 core/src/test/kotlin/com/alecstrong/sql/psi/core/GetForeignKeyHelperTest.kt diff --git a/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt b/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt index d771da969..dd9f8c7dc 100644 --- a/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt +++ b/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt @@ -3,11 +3,15 @@ package com.alecstrong.sql.psi.core.psi.mixins import com.alecstrong.sql.psi.core.AnnotationException import com.alecstrong.sql.psi.core.SqlAnnotationHolder import com.alecstrong.sql.psi.core.SqlParser +import com.alecstrong.sql.psi.core.psi.SqlColumnDef +import com.alecstrong.sql.psi.core.psi.SqlColumnName import com.alecstrong.sql.psi.core.psi.SqlColumnReference +import com.alecstrong.sql.psi.core.psi.SqlCreateTableStmt import com.alecstrong.sql.psi.core.psi.SqlNamedElementImpl import com.intellij.icons.AllIcons import com.intellij.lang.ASTNode import com.intellij.lang.PsiBuilder +import com.intellij.psi.util.parentOfType import javax.swing.Icon internal abstract class ColumnNameMixin( @@ -32,3 +36,17 @@ internal abstract class ColumnNameMixin( return AllIcons.Nodes.DataColumn } } + +fun SqlColumnName.getColumnDefOrNull(): SqlColumnDef? { + val tables = tablesAvailable(this) + for (table in tables) { + val tableDef = table.tableName.parentOfType() ?: continue + for (columnDef in tableDef.columnDefList) { + val name = columnDef.columnName.name + if (name == this.name) { + return columnDef + } + } + } + return null +} diff --git a/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/CreateTableMixin.kt b/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/CreateTableMixin.kt index 4d4c8cc2b..3a6dea5c7 100644 --- a/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/CreateTableMixin.kt +++ b/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/CreateTableMixin.kt @@ -207,10 +207,11 @@ internal abstract class CreateTableMixin private constructor( } } - tableConstraintList.filter { it.foreignKeyClause != null } + tableConstraintList.filter { it.foreignTableClause != null } .forEach { constraint -> - constraint.foreignKeyClause!!.checkCompositeForeignKey( - constraint.columnNameList, + val foreignTableClause = constraint.foreignTableClause!! + foreignTableClause.foreignKeyClause.checkCompositeForeignKey( + foreignTableClause.columnNameList, ) } } diff --git a/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ForeignKeyClauseMixin.kt b/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ForeignKeyClauseMixin.kt index 351a79986..347005366 100644 --- a/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ForeignKeyClauseMixin.kt +++ b/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ForeignKeyClauseMixin.kt @@ -1,10 +1,13 @@ package com.alecstrong.sql.psi.core.psi.mixins import com.alecstrong.sql.psi.core.psi.QueryElement.QueryResult +import com.alecstrong.sql.psi.core.psi.SqlColumnDef import com.alecstrong.sql.psi.core.psi.SqlCompositeElementImpl +import com.alecstrong.sql.psi.core.psi.SqlCreateTableStmt import com.alecstrong.sql.psi.core.psi.SqlForeignKeyClause import com.intellij.lang.ASTNode import com.intellij.psi.PsiElement +import com.intellij.psi.util.parentOfType internal abstract class ForeignKeyClauseMixin( node: ASTNode, @@ -18,3 +21,26 @@ internal abstract class ForeignKeyClauseMixin( return super.queryAvailable(child) } } + +fun SqlColumnDef.isForeignKey(): Boolean { + for (columnConstraint in columnConstraintList) { + if (columnConstraint.foreignKeyClause != null) { + return true + } + } + val createTableStmt: SqlCreateTableStmt? = parentOfType() + if (createTableStmt != null) { + for (tableConstraints in createTableStmt.tableConstraintList) { + val foreignTableClause = tableConstraints.foreignTableClause + if (foreignTableClause != null) { + val columns = foreignTableClause.columnNameList + for (column in columns) { + if (column.name == columnName.name) { + return true + } + } + } + } + } + return false +} diff --git a/core/src/main/kotlin/com/alecstrong/sql/psi/core/sql.bnf b/core/src/main/kotlin/com/alecstrong/sql/psi/core/sql.bnf index 10035cc21..039206105 100644 --- a/core/src/main/kotlin/com/alecstrong/sql/psi/core/sql.bnf +++ b/core/src/main/kotlin/com/alecstrong/sql/psi/core/sql.bnf @@ -126,7 +126,8 @@ generated_clause ::= [ GENERATED ] ALWAYS AS LP expr RP check_constraint ::= CHECK LP expr RP default_constraint ::= DEFAULT ( signed_number | literal_value | LP expr RP ) signed_number ::= [ PLUS | MINUS ] numeric_literal -table_constraint ::= [ CONSTRAINT identifier ] ( ( PRIMARY KEY | UNIQUE ) LP indexed_column ( COMMA indexed_column ) * RP conflict_clause | CHECK LP expr RP | FOREIGN KEY LP column_name ( COMMA column_name ) * RP foreign_key_clause ) +table_constraint ::= [ CONSTRAINT identifier ] ( ( PRIMARY KEY | UNIQUE ) LP indexed_column ( COMMA indexed_column ) * RP conflict_clause | CHECK LP expr RP | foreign_table_clause ) +foreign_table_clause ::= FOREIGN KEY LP column_name ( COMMA column_name ) * RP foreign_key_clause foreign_key_clause ::= REFERENCES foreign_table [ LP column_name ( COMMA column_name ) * RP ] [ ( ON ( DELETE | UPDATE ) ( SET NULL | SET DEFAULT | CASCADE | RESTRICT | NO ACTION ) | MATCH identifier ) * ] [ [ NOT ] DEFERRABLE [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] ] { mixin = "com.alecstrong.sql.psi.core.psi.mixins.ForeignKeyClauseMixin" } diff --git a/core/src/test/kotlin/com/alecstrong/sql/psi/core/GetForeignKeyHelperTest.kt b/core/src/test/kotlin/com/alecstrong/sql/psi/core/GetForeignKeyHelperTest.kt new file mode 100644 index 000000000..3855649e5 --- /dev/null +++ b/core/src/test/kotlin/com/alecstrong/sql/psi/core/GetForeignKeyHelperTest.kt @@ -0,0 +1,57 @@ +package com.alecstrong.sql.psi.core + +import com.alecstrong.sql.psi.core.psi.SqlColumnExpr +import com.alecstrong.sql.psi.core.psi.mixins.getColumnDefOrNull +import com.alecstrong.sql.psi.core.psi.mixins.isForeignKey +import com.alecstrong.sql.psi.test.fixtures.compileFile +import com.google.common.truth.Truth.assertThat +import org.junit.Test + +class GetForeignKeyHelperTest { + @Test + fun columnConstraint() { + val sqlFile = compileFile( + """ + |CREATE TABLE foo ( + | id INT PRIMARY KEY + |); + | + |CREATE TABLE bar ( + |a TEXT REFERENCES foo(id) + |); + | + |SELECT a FROM bar; + """.trimMargin(), + ) + val select = sqlFile.sqlStmtList!!.stmtList.last() + val a = (select.compoundSelectStmt!!.selectStmtList.single().resultColumnList.single().expr as SqlColumnExpr).columnName + + val columnDef = a.getColumnDefOrNull() + assertThat(columnDef).isNotNull() + assertThat(columnDef!!.isForeignKey()).isTrue() + } + + @Test + fun tableConstraint() { + val sqlFile = compileFile( + """ + |CREATE TABLE foo ( + | id INT PRIMARY KEY + |); + | + |CREATE TABLE bar ( + |a TEXT, + |FOREIGN KEY (a) REFERENCES foo(id) + |); + | + |SELECT a FROM bar; + """.trimMargin(), + ) + val select = sqlFile.sqlStmtList!!.stmtList.last() + val a = (select.compoundSelectStmt!!.selectStmtList.single().resultColumnList.single().expr as SqlColumnExpr).columnName + + val columnDef = a.getColumnDefOrNull() + assertThat(columnDef).isNotNull() + assertThat(columnDef!!.isForeignKey()).isTrue() + } +} From 80728b3da0dda71623f886ee476eb1764d52e81f Mon Sep 17 00:00:00 2001 From: hfhbd Date: Fri, 4 Aug 2023 12:11:15 +0200 Subject: [PATCH 2/4] Use reference and not error-prone name --- .../alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt | 5 +++-- .../sql/psi/core/psi/mixins/ForeignKeyClauseMixin.kt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt b/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt index dd9f8c7dc..823513f8b 100644 --- a/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt +++ b/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt @@ -38,12 +38,13 @@ internal abstract class ColumnNameMixin( } fun SqlColumnName.getColumnDefOrNull(): SqlColumnDef? { + val ref = reference?.resolve() ?: return null val tables = tablesAvailable(this) for (table in tables) { val tableDef = table.tableName.parentOfType() ?: continue for (columnDef in tableDef.columnDefList) { - val name = columnDef.columnName.name - if (name == this.name) { + val columnRef = columnDef.columnName.reference + if (columnRef != null && columnRef.resolve() == ref) { return columnDef } } diff --git a/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ForeignKeyClauseMixin.kt b/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ForeignKeyClauseMixin.kt index 347005366..63894a97e 100644 --- a/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ForeignKeyClauseMixin.kt +++ b/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ForeignKeyClauseMixin.kt @@ -35,7 +35,7 @@ fun SqlColumnDef.isForeignKey(): Boolean { if (foreignTableClause != null) { val columns = foreignTableClause.columnNameList for (column in columns) { - if (column.name == columnName.name) { + if (column.reference?.resolve() == columnName) { return true } } From aea3f16c0188657ad60ae24c1ecbce4782b0d8a8 Mon Sep 17 00:00:00 2001 From: hfhbd Date: Fri, 4 Aug 2023 13:28:37 +0200 Subject: [PATCH 3/4] Simplify getColumnDef logic --- .../psi/core/psi/mixins/ColumnNameMixin.kt | 15 +------- .../sql/psi/core/GetForeignKeyHelperTest.kt | 34 ++++++++++++++++++- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt b/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt index 823513f8b..0a34f8f22 100644 --- a/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt +++ b/core/src/main/kotlin/com/alecstrong/sql/psi/core/psi/mixins/ColumnNameMixin.kt @@ -6,12 +6,10 @@ import com.alecstrong.sql.psi.core.SqlParser import com.alecstrong.sql.psi.core.psi.SqlColumnDef import com.alecstrong.sql.psi.core.psi.SqlColumnName import com.alecstrong.sql.psi.core.psi.SqlColumnReference -import com.alecstrong.sql.psi.core.psi.SqlCreateTableStmt import com.alecstrong.sql.psi.core.psi.SqlNamedElementImpl import com.intellij.icons.AllIcons import com.intellij.lang.ASTNode import com.intellij.lang.PsiBuilder -import com.intellij.psi.util.parentOfType import javax.swing.Icon internal abstract class ColumnNameMixin( @@ -38,16 +36,5 @@ internal abstract class ColumnNameMixin( } fun SqlColumnName.getColumnDefOrNull(): SqlColumnDef? { - val ref = reference?.resolve() ?: return null - val tables = tablesAvailable(this) - for (table in tables) { - val tableDef = table.tableName.parentOfType() ?: continue - for (columnDef in tableDef.columnDefList) { - val columnRef = columnDef.columnName.reference - if (columnRef != null && columnRef.resolve() == ref) { - return columnDef - } - } - } - return null + return reference?.resolve()?.parent as? SqlColumnDef } diff --git a/core/src/test/kotlin/com/alecstrong/sql/psi/core/GetForeignKeyHelperTest.kt b/core/src/test/kotlin/com/alecstrong/sql/psi/core/GetForeignKeyHelperTest.kt index 3855649e5..d1069bec6 100644 --- a/core/src/test/kotlin/com/alecstrong/sql/psi/core/GetForeignKeyHelperTest.kt +++ b/core/src/test/kotlin/com/alecstrong/sql/psi/core/GetForeignKeyHelperTest.kt @@ -40,7 +40,7 @@ class GetForeignKeyHelperTest { |); | |CREATE TABLE bar ( - |a TEXT, + |a INT, |FOREIGN KEY (a) REFERENCES foo(id) |); | @@ -54,4 +54,36 @@ class GetForeignKeyHelperTest { assertThat(columnDef).isNotNull() assertThat(columnDef!!.isForeignKey()).isTrue() } + + @Test + fun alterTable() { + compileFile( + """ + |CREATE TABLE foo ( + | id INT PRIMARY KEY + |); + | + |CREATE TABLE bar ( + |b TEXT + |); + """.trimMargin(), + fileName = "1.s", + ) + val alterFile = compileFile( + """ + |ALTER TABLE bar + | ADD COLUMN a INT REFERENCES foo(id) + |; + | + |SELECT a FROM bar; + """.trimMargin(), + fileName = "2.s", + ) + val select = alterFile.sqlStmtList!!.stmtList.last() + val a = (select.compoundSelectStmt!!.selectStmtList.single().resultColumnList.single().expr as SqlColumnExpr).columnName + + val columnDef = a.getColumnDefOrNull() + assertThat(columnDef).isNotNull() + assertThat(columnDef!!.isForeignKey()).isTrue() + } } From 38d4b6cb3e6256be0639237bf1eae03a0754bdf2 Mon Sep 17 00:00:00 2001 From: hfhbd Date: Fri, 4 Aug 2023 14:15:21 +0200 Subject: [PATCH 4/4] Fix test --- .../sql/psi/core/GetForeignKeyHelperTest.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/src/test/kotlin/com/alecstrong/sql/psi/core/GetForeignKeyHelperTest.kt b/core/src/test/kotlin/com/alecstrong/sql/psi/core/GetForeignKeyHelperTest.kt index d1069bec6..49d691331 100644 --- a/core/src/test/kotlin/com/alecstrong/sql/psi/core/GetForeignKeyHelperTest.kt +++ b/core/src/test/kotlin/com/alecstrong/sql/psi/core/GetForeignKeyHelperTest.kt @@ -5,9 +5,23 @@ import com.alecstrong.sql.psi.core.psi.mixins.getColumnDefOrNull import com.alecstrong.sql.psi.core.psi.mixins.isForeignKey import com.alecstrong.sql.psi.test.fixtures.compileFile import com.google.common.truth.Truth.assertThat +import org.junit.After +import org.junit.Before import org.junit.Test +import java.io.File class GetForeignKeyHelperTest { + @Before + fun before() { + File("build/tmp").deleteRecursively() + } + + @After + fun after() { + SqlParserUtil.reset() + File("build/tmp").deleteRecursively() + } + @Test fun columnConstraint() { val sqlFile = compileFile(