Skip to content

Commit e088fd6

Browse files
hayssamsclaude
andcommitted
Support DuckDB ATTACH and DETACH statements
Adds ATTACH [DATABASE] [IF NOT EXISTS] path [AS alias] [(options)] and DETACH [DATABASE] [IF EXISTS] name, with options such as READ_ONLY or TYPE SQLITE kept as strings. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EqinwHBKuAtPEmtXr3b5P2
1 parent 7d4eac7 commit e088fd6

10 files changed

Lines changed: 423 additions & 0 deletions

File tree

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement;
11+
12+
import net.sf.jsqlparser.statement.select.PlainSelect;
13+
14+
import java.util.List;
15+
16+
/**
17+
* DuckDB's {@code ATTACH [DATABASE] [IF NOT EXISTS] path [AS alias] [(options)]}, which adds another
18+
* database file or remote database to the session.
19+
*
20+
* @see <a href="https://duckdb.org/docs/stable/sql/statements/attach">ATTACH</a>
21+
*/
22+
public class AttachStatement implements Statement {
23+
private String databasePath;
24+
private String alias;
25+
private boolean usingDatabaseKeyword;
26+
private boolean ifNotExists;
27+
private List<String> options;
28+
29+
public String getDatabasePath() {
30+
return databasePath;
31+
}
32+
33+
public void setDatabasePath(String databasePath) {
34+
this.databasePath = databasePath;
35+
}
36+
37+
public AttachStatement withDatabasePath(String databasePath) {
38+
setDatabasePath(databasePath);
39+
return this;
40+
}
41+
42+
public String getAlias() {
43+
return alias;
44+
}
45+
46+
public void setAlias(String alias) {
47+
this.alias = alias;
48+
}
49+
50+
public AttachStatement withAlias(String alias) {
51+
setAlias(alias);
52+
return this;
53+
}
54+
55+
public boolean isUsingDatabaseKeyword() {
56+
return usingDatabaseKeyword;
57+
}
58+
59+
public void setUsingDatabaseKeyword(boolean usingDatabaseKeyword) {
60+
this.usingDatabaseKeyword = usingDatabaseKeyword;
61+
}
62+
63+
public AttachStatement withUsingDatabaseKeyword(boolean usingDatabaseKeyword) {
64+
setUsingDatabaseKeyword(usingDatabaseKeyword);
65+
return this;
66+
}
67+
68+
public boolean isIfNotExists() {
69+
return ifNotExists;
70+
}
71+
72+
public void setIfNotExists(boolean ifNotExists) {
73+
this.ifNotExists = ifNotExists;
74+
}
75+
76+
public AttachStatement withIfNotExists(boolean ifNotExists) {
77+
setIfNotExists(ifNotExists);
78+
return this;
79+
}
80+
81+
public List<String> getOptions() {
82+
return options;
83+
}
84+
85+
public void setOptions(List<String> options) {
86+
this.options = options;
87+
}
88+
89+
public AttachStatement withOptions(List<String> options) {
90+
setOptions(options);
91+
return this;
92+
}
93+
94+
public StringBuilder appendTo(StringBuilder builder) {
95+
builder.append("ATTACH ");
96+
if (usingDatabaseKeyword) {
97+
builder.append("DATABASE ");
98+
}
99+
if (ifNotExists) {
100+
builder.append("IF NOT EXISTS ");
101+
}
102+
builder.append(databasePath);
103+
if (alias != null) {
104+
builder.append(" AS ").append(alias);
105+
}
106+
if (options != null && !options.isEmpty()) {
107+
builder.append(" ").append(PlainSelect.getStringList(options, true, true));
108+
}
109+
return builder;
110+
}
111+
112+
@Override
113+
public String toString() {
114+
return appendTo(new StringBuilder()).toString();
115+
}
116+
117+
@Override
118+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
119+
return statementVisitor.visit(this, context);
120+
}
121+
}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement;
11+
12+
/**
13+
* DuckDB's {@code DETACH [DATABASE] [IF EXISTS] name}, the counterpart of
14+
* {@link AttachStatement}.
15+
*
16+
* @see <a href="https://duckdb.org/docs/stable/sql/statements/attach">DETACH</a>
17+
*/
18+
public class DetachStatement implements Statement {
19+
private String databaseName;
20+
private boolean usingDatabaseKeyword;
21+
private boolean ifExists;
22+
23+
public DetachStatement() {}
24+
25+
public DetachStatement(String databaseName) {
26+
this.databaseName = databaseName;
27+
}
28+
29+
public String getDatabaseName() {
30+
return databaseName;
31+
}
32+
33+
public void setDatabaseName(String databaseName) {
34+
this.databaseName = databaseName;
35+
}
36+
37+
public DetachStatement withDatabaseName(String databaseName) {
38+
setDatabaseName(databaseName);
39+
return this;
40+
}
41+
42+
public boolean isUsingDatabaseKeyword() {
43+
return usingDatabaseKeyword;
44+
}
45+
46+
public void setUsingDatabaseKeyword(boolean usingDatabaseKeyword) {
47+
this.usingDatabaseKeyword = usingDatabaseKeyword;
48+
}
49+
50+
public DetachStatement withUsingDatabaseKeyword(boolean usingDatabaseKeyword) {
51+
setUsingDatabaseKeyword(usingDatabaseKeyword);
52+
return this;
53+
}
54+
55+
public boolean isIfExists() {
56+
return ifExists;
57+
}
58+
59+
public void setIfExists(boolean ifExists) {
60+
this.ifExists = ifExists;
61+
}
62+
63+
public DetachStatement withIfExists(boolean ifExists) {
64+
setIfExists(ifExists);
65+
return this;
66+
}
67+
68+
public StringBuilder appendTo(StringBuilder builder) {
69+
builder.append("DETACH ");
70+
if (usingDatabaseKeyword) {
71+
builder.append("DATABASE ");
72+
}
73+
if (ifExists) {
74+
builder.append("IF EXISTS ");
75+
}
76+
builder.append(databaseName);
77+
return builder;
78+
}
79+
80+
@Override
81+
public String toString() {
82+
return appendTo(new StringBuilder()).toString();
83+
}
84+
85+
@Override
86+
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
87+
return statementVisitor.visit(this, context);
88+
}
89+
}

‎src/main/java/net/sf/jsqlparser/statement/StatementFeatureVisitor.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -535,6 +535,20 @@ public <S> Void visit(ExtensionStatement extensionStatement, S context) {
535535
return null;
536536
}
537537

538+
@Override
539+
public <S> Void visit(AttachStatement attachStatement, S context) {
540+
analysis.claimTopLevel();
541+
analysis.certain(StmtFeature.MODIFIES_SESSION);
542+
return null;
543+
}
544+
545+
@Override
546+
public <S> Void visit(DetachStatement detachStatement, S context) {
547+
analysis.claimTopLevel();
548+
analysis.certain(StmtFeature.MODIFIES_SESSION);
549+
return null;
550+
}
551+
538552
@Override
539553
public <S> Void visit(PurgeStatement purgeStatement, S context) {
540554
analysis.claimTopLevel();

‎src/main/java/net/sf/jsqlparser/statement/StatementVisitor.java‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,18 @@ default void visit(ExtensionStatement extensionStatement) {
383383
this.visit(extensionStatement, null);
384384
}
385385

386+
<S> T visit(AttachStatement attachStatement, S context);
387+
388+
default void visit(AttachStatement attachStatement) {
389+
this.visit(attachStatement, null);
390+
}
391+
392+
<S> T visit(DetachStatement detachStatement, S context);
393+
394+
default void visit(DetachStatement detachStatement) {
395+
this.visit(detachStatement, null);
396+
}
397+
386398
<S> T visit(PurgeStatement purgeStatement, S context);
387399

388400
default void visit(PurgeStatement purgeStatement) {

‎src/main/java/net/sf/jsqlparser/statement/StatementVisitorAdapter.java‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -628,6 +628,16 @@ public <S> T visit(ExtensionStatement extensionStatement, S context) {
628628
return null;
629629
}
630630

631+
@Override
632+
public <S> T visit(AttachStatement attachStatement, S context) {
633+
return null;
634+
}
635+
636+
@Override
637+
public <S> T visit(DetachStatement detachStatement, S context) {
638+
return null;
639+
}
640+
631641
@Override
632642
public <S> T visit(PurgeStatement purgeStatement, S context) {
633643
return null;

‎src/main/java/net/sf/jsqlparser/util/TablesNamesFinder.java‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@
211211
import net.sf.jsqlparser.statement.upsert.Upsert;
212212
import net.sf.jsqlparser.statement.PragmaStatement;
213213
import net.sf.jsqlparser.statement.ExtensionStatement;
214+
import net.sf.jsqlparser.statement.AttachStatement;
215+
import net.sf.jsqlparser.statement.DetachStatement;
214216

215217

216218
/**
@@ -2492,6 +2494,28 @@ public void visit(ExtensionStatement extensionStatement) {
24922494
StatementVisitor.super.visit(extensionStatement);
24932495
}
24942496

2497+
@Override
2498+
public <S> Void visit(AttachStatement attachStatement, S context) {
2499+
// no tables involved in this statement
2500+
return null;
2501+
}
2502+
2503+
@Override
2504+
public void visit(AttachStatement attachStatement) {
2505+
StatementVisitor.super.visit(attachStatement);
2506+
}
2507+
2508+
@Override
2509+
public <S> Void visit(DetachStatement detachStatement, S context) {
2510+
// no tables involved in this statement
2511+
return null;
2512+
}
2513+
2514+
@Override
2515+
public void visit(DetachStatement detachStatement) {
2516+
StatementVisitor.super.visit(detachStatement);
2517+
}
2518+
24952519
@Override
24962520
public <S> Void visit(PurgeStatement purgeStatement, S context) {
24972521
if (purgeStatement.getPurgeObjectType() == PurgeObjectType.TABLE) {

‎src/main/java/net/sf/jsqlparser/util/deparser/StatementDeParser.java‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@
9999
import net.sf.jsqlparser.statement.upsert.Upsert;
100100
import net.sf.jsqlparser.statement.PragmaStatement;
101101
import net.sf.jsqlparser.statement.ExtensionStatement;
102+
import net.sf.jsqlparser.statement.AttachStatement;
103+
import net.sf.jsqlparser.statement.DetachStatement;
102104

103105
public class StatementDeParser extends AbstractDeParser<Statement>
104106
implements StatementVisitor<StringBuilder> {
@@ -570,6 +572,18 @@ public <S> StringBuilder visit(ExtensionStatement extensionStatement, S context)
570572
return builder;
571573
}
572574

575+
@Override
576+
public <S> StringBuilder visit(AttachStatement attachStatement, S context) {
577+
attachStatement.appendTo(builder);
578+
return builder;
579+
}
580+
581+
@Override
582+
public <S> StringBuilder visit(DetachStatement detachStatement, S context) {
583+
detachStatement.appendTo(builder);
584+
return builder;
585+
}
586+
573587
@Override
574588
public <S> StringBuilder visit(PurgeStatement purgeStatement, S context) {
575589
purgeStatement.appendTo(builder);

‎src/main/java/net/sf/jsqlparser/util/validation/validator/StatementValidator.java‎

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@
101101
import net.sf.jsqlparser.util.validation.metadata.NamedObject;
102102
import net.sf.jsqlparser.statement.PragmaStatement;
103103
import net.sf.jsqlparser.statement.ExtensionStatement;
104+
import net.sf.jsqlparser.statement.AttachStatement;
105+
import net.sf.jsqlparser.statement.DetachStatement;
104106

105107
/**
106108
* @author gitmotte
@@ -491,6 +493,18 @@ public <S> Void visit(ExtensionStatement extensionStatement, S context) {
491493
return null;
492494
}
493495

496+
@Override
497+
public <S> Void visit(AttachStatement attachStatement, S context) {
498+
// TODO: not yet implemented
499+
return null;
500+
}
501+
502+
@Override
503+
public <S> Void visit(DetachStatement detachStatement, S context) {
504+
// TODO: not yet implemented
505+
return null;
506+
}
507+
494508
@Override
495509
public <S> Void visit(PurgeStatement purgeStatement, S context) {
496510
// TODO: not yet implemented
@@ -715,6 +729,14 @@ public void visit(ExtensionStatement extensionStatement) {
715729
visit(extensionStatement, null);
716730
}
717731

732+
public void visit(AttachStatement attachStatement) {
733+
visit(attachStatement, null);
734+
}
735+
736+
public void visit(DetachStatement detachStatement) {
737+
visit(detachStatement, null);
738+
}
739+
718740
public void visit(PurgeStatement purgeStatement) {
719741
visit(purgeStatement, null);
720742
}

0 commit comments

Comments
 (0)