Skip to content
Closed
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 @@ -3,6 +3,8 @@ 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.SqlNamedElementImpl
import com.intellij.icons.AllIcons
Expand Down Expand Up @@ -32,3 +34,7 @@ internal abstract class ColumnNameMixin(
return AllIcons.Nodes.DataColumn
}
}

fun SqlColumnName.getColumnDefOrNull(): SqlColumnDef? {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's a little more nuanced then this, since columns can come from an alter table statement

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also pretty sure this is already done elsewhere in code quite a bit (going from column name to column def)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, you are right. Also added a test for alter table.

return reference?.resolve()?.parent as? SqlColumnDef
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -18,3 +21,26 @@ internal abstract class ForeignKeyClauseMixin(
return super.queryAvailable(child)
}
}

fun SqlColumnDef.isForeignKey(): Boolean {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After thinking again, this won't work in sqldelight with the mysql dialect supporting ALTER TABLE DROP FOREIGN KEY because this isn't a SqlColumnDef... I guess the only option is to put the keys into the schema too 🤔

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.reference?.resolve() == columnName) {
return true
}
}
}
}
}
return false
}
3 changes: 2 additions & 1 deletion core/src/main/kotlin/com/alecstrong/sql/psi/core/sql.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
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.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(
"""
|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 INT,
|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()
}

@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()
}
}