PageRenderTime 57ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 0ms

/projects/netbeans-7.3/db.sql.editor/src/org/netbeans/modules/db/sql/editor/completion/SQLCompletionQuery.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 1043 lines | 867 code | 49 blank | 127 comment | 224 complexity | 80e1df0baf538b2b254f294ccad2ee9d MD5 | raw file
  1. /*
  2. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3. *
  4. * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
  5. *
  6. * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
  7. * Other names may be trademarks of their respective owners.
  8. *
  9. * The contents of this file are subject to the terms of either the GNU
  10. * General Public License Version 2 only ("GPL") or the Common
  11. * Development and Distribution License("CDDL") (collectively, the
  12. * "License"). You may not use this file except in compliance with the
  13. * License. You can obtain a copy of the License at
  14. * http://www.netbeans.org/cddl-gplv2.html
  15. * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
  16. * specific language governing permissions and limitations under the
  17. * License. When distributing the software, include this License Header
  18. * Notice in each file and include the License file at
  19. * nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
  20. * particular file as subject to the "Classpath" exception as provided
  21. * by Oracle in the GPL Version 2 section of the License file that
  22. * accompanied this code. If applicable, add the following below the
  23. * License Header, with the fields enclosed by brackets [] replaced by
  24. * your own identifying information:
  25. * "Portions Copyrighted [year] [name of copyright owner]"
  26. *
  27. * If you wish your version of this file to be governed by only the CDDL
  28. * or only the GPL Version 2, indicate your decision by adding
  29. * "[Contributor] elects to include this software in this distribution
  30. * under the [CDDL or GPL Version 2] license." If you do not indicate a
  31. * single choice of license, a recipient has the option to distribute
  32. * your version of this file under either the CDDL, the GPL Version 2 or
  33. * to extend the choice of license to its licensees as provided above.
  34. * However, if you add GPL Version 2 code and therefore, elected the GPL
  35. * Version 2 license, then the option applies only if the new code is
  36. * made subject to such option by the copyright holder.
  37. *
  38. * Contributor(s):
  39. *
  40. * Portions Copyrighted 2009 Sun Microsystems, Inc.
  41. */
  42. package org.netbeans.modules.db.sql.editor.completion;
  43. import org.netbeans.modules.db.sql.analyzer.SQLStatementAnalyzer;
  44. import java.sql.Connection;
  45. import java.sql.DatabaseMetaData;
  46. import java.sql.SQLException;
  47. import java.util.ArrayList;
  48. import java.util.Arrays;
  49. import java.util.Collections;
  50. import java.util.HashSet;
  51. import java.util.LinkedHashSet;
  52. import java.util.List;
  53. import java.util.Map;
  54. import java.util.Map.Entry;
  55. import java.util.Set;
  56. import java.util.TreeMap;
  57. import java.util.TreeSet;
  58. import java.util.logging.Level;
  59. import java.util.logging.Logger;
  60. import javax.swing.text.Document;
  61. import org.netbeans.api.db.explorer.DatabaseConnection;
  62. import org.netbeans.api.db.sql.support.SQLIdentifiers;
  63. import org.netbeans.api.db.sql.support.SQLIdentifiers.Quoter;
  64. import org.netbeans.api.lexer.TokenSequence;
  65. import org.netbeans.modules.db.metadata.model.api.Action;
  66. import org.netbeans.modules.db.metadata.model.api.Catalog;
  67. import org.netbeans.modules.db.metadata.model.api.Metadata;
  68. import org.netbeans.modules.db.metadata.model.api.MetadataModelException;
  69. import org.netbeans.modules.db.api.metadata.DBConnMetadataModelManager;
  70. import org.netbeans.modules.db.metadata.model.api.*;
  71. import org.netbeans.modules.db.sql.analyzer.CreateStatement;
  72. import org.netbeans.modules.db.sql.analyzer.DeleteStatement;
  73. import org.netbeans.modules.db.sql.analyzer.TablesClause;
  74. import org.netbeans.modules.db.sql.analyzer.InsertStatement;
  75. import org.netbeans.modules.db.sql.analyzer.QualIdent;
  76. import org.netbeans.modules.db.sql.analyzer.SQLStatement;
  77. import org.netbeans.modules.db.sql.analyzer.SQLStatement.Context;
  78. import org.netbeans.modules.db.sql.analyzer.SelectStatement;
  79. import org.netbeans.modules.db.sql.analyzer.SQLStatementKind;
  80. import org.netbeans.modules.db.sql.analyzer.UpdateStatement;
  81. import org.netbeans.modules.db.sql.editor.api.completion.SQLCompletionResultSet;
  82. import org.netbeans.modules.db.sql.lexer.SQLTokenId;
  83. import org.netbeans.spi.editor.completion.CompletionResultSet;
  84. import org.netbeans.spi.editor.completion.support.AsyncCompletionQuery;
  85. import org.openide.DialogDisplayer;
  86. import org.openide.NotifyDescriptor;
  87. import org.openide.util.NbBundle;
  88. /**
  89. *
  90. * @author Andrei Badea
  91. */
  92. public class SQLCompletionQuery extends AsyncCompletionQuery {
  93. private static final Logger LOGGER = Logger.getLogger(SQLCompletionQuery.class.getName());
  94. // XXX quoted identifiers.
  95. private final DatabaseConnection dbconn;
  96. private Metadata metadata;
  97. private SQLCompletionEnv env;
  98. private Quoter quoter;
  99. private SQLStatement statement;
  100. /** All tables (views possible) available for completion in current offset. */
  101. private TablesClause tablesClause;
  102. // ugly but likely the best way to add views to cc for SELECTs
  103. private boolean includeViews = false;
  104. private int anchorOffset = -1; // Relative to statement offset.
  105. private int substitutionOffset = 0; // Relative to statement offset.
  106. private SQLCompletionItems items;
  107. /** Context in SQL statement. */
  108. private Context context;
  109. /** Recognized identifier (also incomplete) in SQL statement. */
  110. private Identifier ident;
  111. public SQLCompletionQuery(DatabaseConnection dbconn) {
  112. this.dbconn = dbconn;
  113. }
  114. @Override
  115. protected void query(CompletionResultSet resultSet, final Document doc, final int caretOffset) {
  116. doQuery(SQLCompletionEnv.forDocument(doc, caretOffset));
  117. if (items != null) {
  118. items.fill(resultSet);
  119. }
  120. if (anchorOffset != -1) {
  121. resultSet.setAnchorOffset(env.getStatementOffset() + anchorOffset);
  122. }
  123. resultSet.finish();
  124. }
  125. public void query(SQLCompletionResultSet resultSet, SQLCompletionEnv newEnv) {
  126. doQuery(newEnv);
  127. if (items != null) {
  128. items.fill(resultSet);
  129. }
  130. if (anchorOffset != -1) {
  131. resultSet.setAnchorOffset(newEnv.getStatementOffset() + anchorOffset);
  132. }
  133. }
  134. private void doQuery(final SQLCompletionEnv newEnv) {
  135. try {
  136. DBConnMetadataModelManager.get(dbconn).runReadAction(new Action<Metadata>() {
  137. @Override
  138. public void run(Metadata metadata) {
  139. Connection conn = dbconn.getJDBCConnection();
  140. if (conn == null) {
  141. return;
  142. }
  143. Quoter quoter = null;
  144. try {
  145. DatabaseMetaData dmd = conn.getMetaData();
  146. quoter = SQLIdentifiers.createQuoter(dmd);
  147. } catch (SQLException e) {
  148. throw new MetadataException(e);
  149. }
  150. doQuery(newEnv, metadata, quoter);
  151. }
  152. });
  153. } catch (MetadataModelException e) {
  154. reportError(e);
  155. }
  156. }
  157. // Called by unit tests.
  158. SQLCompletionItems doQuery(SQLCompletionEnv env, Metadata metadata, Quoter quoter) {
  159. this.env = env;
  160. this.metadata = metadata;
  161. this.quoter = quoter;
  162. anchorOffset = -1;
  163. substitutionOffset = 0;
  164. items = new SQLCompletionItems(quoter, env.getSubstitutionHandler());
  165. if (env.getTokenSequence().isEmpty()) {
  166. completeKeyword("SELECT", "INSERT", "DELETE", "DROP", "UPDATE"); //NOI18N
  167. return items;
  168. }
  169. statement = SQLStatementAnalyzer.analyze(env.getTokenSequence(), quoter);
  170. if (statement == null) {
  171. completeKeyword("SELECT", "INSERT", "DELETE", "DROP", "UPDATE"); //NOI18N
  172. return items;
  173. }
  174. if (statement.getKind() == SQLStatementKind.CREATE && ((CreateStatement) statement).hasBody()) {
  175. completeCreateBody();
  176. return items;
  177. }
  178. context = statement.getContextAtOffset(env.getCaretOffset());
  179. if (context == null) {
  180. completeKeyword("SELECT", "INSERT", "DELETE", "DROP", "UPDATE"); //NOI18N
  181. return items;
  182. }
  183. ident = findIdentifier();
  184. if (ident == null) {
  185. completeKeyword(context);
  186. return items;
  187. }
  188. anchorOffset = ident.anchorOffset;
  189. substitutionOffset = ident.substitutionOffset;
  190. SQLStatementKind kind = statement.getKind();
  191. switch (kind) {
  192. case SELECT:
  193. completeSelect();
  194. break;
  195. case INSERT:
  196. completeInsert();
  197. break;
  198. case DROP:
  199. completeDrop();
  200. break;
  201. case UPDATE:
  202. completeUpdate();
  203. break;
  204. case DELETE:
  205. completeDelete();
  206. break;
  207. }
  208. return items;
  209. }
  210. private void completeSelect() {
  211. SelectStatement selectStatement = (SelectStatement) statement;
  212. tablesClause = selectStatement.getTablesInEffect(env.getCaretOffset());
  213. includeViews = true;
  214. switch (context) {
  215. case SELECT:
  216. completeColumn(ident);
  217. break;
  218. case FROM:
  219. completeTuple(ident);
  220. break;
  221. case JOIN_CONDITION:
  222. completeColumnWithDefinedTuple(ident);
  223. break;
  224. case WHERE:
  225. if (tablesClause != null) {
  226. completeColumnWithDefinedTuple(ident);
  227. } else {
  228. completeColumn(ident);
  229. }
  230. break;
  231. case ORDER:
  232. case GROUP:
  233. completeKeyword(context);
  234. break;
  235. default:
  236. if (tablesClause != null) {
  237. completeColumnWithDefinedTuple(ident);
  238. }
  239. }
  240. }
  241. private void completeInsert () {
  242. InsertStatement insertStatement = (InsertStatement) statement;
  243. includeViews = false;
  244. switch (context) {
  245. case INSERT:
  246. completeKeyword(context);
  247. break;
  248. case INSERT_INTO:
  249. completeTuple(ident);
  250. break;
  251. case COLUMNS:
  252. insideColumns (ident, resolveTuple(insertStatement.getTable ()));
  253. break;
  254. case VALUES:
  255. break;
  256. }
  257. }
  258. private void completeDrop() {
  259. includeViews = false;
  260. switch (context) {
  261. case DROP:
  262. completeKeyword(context);
  263. break;
  264. case DROP_TABLE:
  265. completeTuple(ident);
  266. break;
  267. default:
  268. }
  269. }
  270. private void completeUpdate() {
  271. UpdateStatement updateStatement = (UpdateStatement) statement;
  272. tablesClause = updateStatement.getTablesInEffect(env.getCaretOffset());
  273. includeViews = false;
  274. switch (context) {
  275. case UPDATE:
  276. completeTuple(ident);
  277. break;
  278. case JOIN_CONDITION:
  279. completeColumnWithDefinedTuple(ident);
  280. break;
  281. case SET:
  282. completeColumn(ident);
  283. break;
  284. default:
  285. if (!updateStatement.getSubqueries().isEmpty()) {
  286. completeSelect();
  287. } else if (tablesClause != null) {
  288. completeColumnWithDefinedTuple(ident);
  289. }
  290. }
  291. }
  292. private void completeDelete() {
  293. DeleteStatement deleteStatement = (DeleteStatement) statement;
  294. tablesClause = deleteStatement.getTablesInEffect(env.getCaretOffset());
  295. includeViews = false;
  296. switch (context) {
  297. case DELETE:
  298. completeKeyword(context);
  299. completeTuple(ident);
  300. break;
  301. case FROM:
  302. completeTuple(ident);
  303. break;
  304. case JOIN_CONDITION:
  305. completeColumnWithDefinedTuple(ident);
  306. break;
  307. case WHERE:
  308. if (tablesClause != null) {
  309. completeColumnWithDefinedTuple(ident);
  310. } else {
  311. completeColumn(ident);
  312. }
  313. break;
  314. default:
  315. if (tablesClause != null) {
  316. completeColumnWithDefinedTuple(ident);
  317. }
  318. }
  319. }
  320. /** Provides code completion for body of create procedure/function statement. */
  321. private void completeCreateBody() {
  322. CreateStatement createStatement = (CreateStatement) statement;
  323. String body = env.getStatement().substring(createStatement.getBodyStartOffset(), createStatement.getBodyEndOffset());
  324. // caret offset within body
  325. int caretOffset = env.getCaretOffset() - createStatement.getBodyStartOffset();
  326. // offset of body script in document
  327. int scriptOffset = env.getStatementOffset() + createStatement.getBodyStartOffset();
  328. // process body script
  329. doQuery(SQLCompletionEnv.forScript(body, caretOffset, scriptOffset), metadata, quoter);
  330. // adjust anchor for displaying code completion popup
  331. anchorOffset += scriptOffset;
  332. }
  333. /** Adds keyword/s according to typed prefix and given context. */
  334. private void completeKeyword(Context context) {
  335. switch (context) {
  336. case SELECT:
  337. completeKeyword("FROM"); //NOI18N
  338. break;
  339. case DELETE:
  340. completeKeyword("FROM"); //NOI18N
  341. break;
  342. case INSERT:
  343. completeKeyword("INTO"); //NOI18N
  344. break;
  345. case INSERT_INTO:
  346. case COLUMNS:
  347. completeKeyword("VALUES"); //NOI18N
  348. break;
  349. case FROM:
  350. completeKeyword("WHERE", "GROUP", "ORDER"); //NOI18N
  351. // with join keywors
  352. //completeKeyword("WHERE", "INNER", "OUTER", "LEFT", "JOIN", "ON"); //NOI18N
  353. break;
  354. case UPDATE:
  355. completeKeyword("SET"); //NOI18N
  356. // with join keywors
  357. //completeKeyword("WHERE", "INNER", "OUTER", "LEFT", "JOIN", "ON"); //NOI18N
  358. break;
  359. case JOIN_CONDITION:
  360. completeKeyword("WHERE"); //NOI18N
  361. break;
  362. case SET:
  363. completeKeyword("WHERE"); //NOI18N
  364. break;
  365. case WHERE:
  366. completeKeyword("GROUP", "ORDER"); //NOI18N
  367. break;
  368. case ORDER:
  369. case GROUP:
  370. completeKeyword("BY"); //NOI18N
  371. break;
  372. case GROUP_BY:
  373. completeKeyword("HAVING"); //NOI18N
  374. break;
  375. case DROP:
  376. completeKeyword("TABLE"); //NOI18N
  377. break;
  378. case DROP_TABLE:
  379. case HAVING:
  380. case ORDER_BY:
  381. case VALUES:
  382. // nothing to complete
  383. break;
  384. }
  385. }
  386. /** Adds listed keyword/s according to typed prefix. */
  387. private void completeKeyword(String... keywords) {
  388. Arrays.sort(keywords);
  389. Symbol prefix = findPrefix();
  390. substitutionOffset = prefix.substitutionOffset;
  391. anchorOffset = substitutionOffset;
  392. items.addKeywords(prefix.lastPrefix, substitutionOffset, keywords);
  393. }
  394. /** Adds columns, tuples, schemas and catalogs according to given identifier. */
  395. private void completeColumn(Identifier ident) {
  396. if (ident.fullyTypedIdent.isEmpty()) {
  397. completeColumnSimpleIdent(ident.lastPrefix, ident.quoted);
  398. } else {
  399. completeColumnQualIdent(ident.fullyTypedIdent, ident.lastPrefix, ident.quoted);
  400. }
  401. }
  402. private void insideColumns (Identifier ident, Tuple tuple) {
  403. if (ident.fullyTypedIdent.isEmpty()) {
  404. if (tuple == null) {
  405. completeColumnWithTupleIfSimpleIdent (ident.lastPrefix, ident.quoted);
  406. } else {
  407. items.addColumns (tuple, ident.lastPrefix, ident.quoted, substitutionOffset);
  408. }
  409. } else {
  410. if (tuple == null) {
  411. completeColumnWithTupleIfQualIdent (ident.fullyTypedIdent, ident.lastPrefix, ident.quoted);
  412. } else {
  413. items.addColumns (tuple, ident.lastPrefix, ident.quoted, substitutionOffset);
  414. }
  415. }
  416. }
  417. /** Adds tuples, schemas and catalogs according to given identifier. */
  418. private void completeTuple(Identifier ident) {
  419. if (ident.fullyTypedIdent.isEmpty()) {
  420. completeTupleSimpleIdent(ident.lastPrefix, ident.quoted);
  421. } else if (ident.fullyTypedIdent.isSimple()) {
  422. completeTupleQualIdent(ident.fullyTypedIdent, ident.lastPrefix, ident.quoted);
  423. }
  424. }
  425. /** Adds columns, tuples, schemas and catalogs according to given identifier
  426. * but only for tuples already defined in statement. */
  427. private void completeColumnWithDefinedTuple(Identifier ident) {
  428. if (ident.fullyTypedIdent.isEmpty()) {
  429. completeSimpleIdentBasedOnFromClause(ident.lastPrefix, ident.quoted);
  430. } else {
  431. completeQualIdentBasedOnFromClause(ident.fullyTypedIdent, ident.lastPrefix, ident.quoted);
  432. }
  433. }
  434. /** Adds columns, tuples, schemas and catalogs according to given identifier. */
  435. private void completeColumnSimpleIdent(String typedPrefix, boolean quoted) {
  436. if (tablesClause != null) {
  437. completeSimpleIdentBasedOnFromClause(typedPrefix, quoted);
  438. } else {
  439. Schema defaultSchema = metadata.getDefaultSchema();
  440. if (defaultSchema != null) {
  441. // All columns in default schema, but only if a prefix has been typed, otherwise there
  442. // would be too many columns.
  443. if (typedPrefix != null) {
  444. for (Table table : defaultSchema.getTables()) {
  445. items.addColumns(table, typedPrefix, quoted, substitutionOffset);
  446. }
  447. if (includeViews) {
  448. for (View view : defaultSchema.getViews()) {
  449. items.addColumns(view, typedPrefix, quoted, substitutionOffset);
  450. }
  451. }
  452. }
  453. // All tuples in default schema.
  454. items.addTables(defaultSchema, null, typedPrefix, quoted, substitutionOffset);
  455. if (includeViews) {
  456. items.addViews(defaultSchema, null, typedPrefix, quoted, substitutionOffset);
  457. }
  458. }
  459. // All schemas.
  460. Catalog defaultCatalog = metadata.getDefaultCatalog();
  461. items.addSchemas(defaultCatalog, null, typedPrefix, quoted, substitutionOffset);
  462. // All catalogs.
  463. items.addCatalogs(metadata, null, typedPrefix, quoted, substitutionOffset);
  464. }
  465. }
  466. private void completeColumnWithTupleIfSimpleIdent(String typedPrefix, boolean quoted) {
  467. Schema defaultSchema = metadata.getDefaultSchema();
  468. if (defaultSchema != null) {
  469. // All columns in default schema, but only if a prefix has been typed, otherwise there
  470. // would be too many columns.
  471. if (typedPrefix != null) {
  472. for (Table table : defaultSchema.getTables()) {
  473. items.addColumnsWithTupleName(table, null, typedPrefix, quoted, substitutionOffset - 1);
  474. }
  475. if (includeViews) {
  476. for (View view : defaultSchema.getViews()) {
  477. items.addColumnsWithTupleName(view, null, typedPrefix, quoted, substitutionOffset - 1);
  478. }
  479. }
  480. } else {
  481. // All tuples in default schema.
  482. items.addTablesAtInsertInto (defaultSchema, null, null, typedPrefix, quoted, substitutionOffset - 1);
  483. if (includeViews) {
  484. items.addViewsAtInsertInto(defaultSchema, null, null, typedPrefix, quoted, substitutionOffset - 1);
  485. }
  486. }
  487. }
  488. // All schemas.
  489. Catalog defaultCatalog = metadata.getDefaultCatalog();
  490. items.addSchemas(defaultCatalog, null, typedPrefix, quoted, substitutionOffset);
  491. // All catalogs.
  492. items.addCatalogs(metadata, null, typedPrefix, quoted, substitutionOffset);
  493. }
  494. private void completeColumnWithTupleIfQualIdent(QualIdent fullyTypedIdent, String lastPrefix, boolean quoted) {
  495. // Assume fullyTypedIdent is a tuple.
  496. Tuple tuple = resolveTuple(fullyTypedIdent);
  497. if (tuple != null) {
  498. items.addColumnsWithTupleName (tuple, fullyTypedIdent, lastPrefix, quoted,
  499. substitutionOffset - 1);
  500. }
  501. // Assume fullyTypedIdent is a schema.
  502. Schema schema = resolveSchema(fullyTypedIdent);
  503. if (schema != null) {
  504. items.addTablesAtInsertInto(schema, fullyTypedIdent, null, lastPrefix, quoted, substitutionOffset - 1);
  505. if (includeViews) {
  506. items.addViewsAtInsertInto(schema, fullyTypedIdent, null, lastPrefix, quoted, substitutionOffset - 1);
  507. }
  508. }
  509. // Assume fullyTypedIdent is a catalog.
  510. Catalog catalog = resolveCatalog(fullyTypedIdent);
  511. if (catalog != null) {
  512. completeCatalog(catalog, lastPrefix, quoted);
  513. }
  514. }
  515. /** Adds columns, tuples, schemas and catalogs according to given identifier. */
  516. private void completeColumnQualIdent(QualIdent fullyTypedIdent, String lastPrefix, boolean quoted) {
  517. if (tablesClause != null) {
  518. completeQualIdentBasedOnFromClause(fullyTypedIdent, lastPrefix, quoted);
  519. } else {
  520. // Assume fullyTypedIdent is a tuple.
  521. Tuple tuple = resolveTuple(fullyTypedIdent);
  522. if (tuple != null) {
  523. items.addColumns(tuple, lastPrefix, quoted, substitutionOffset);
  524. }
  525. // Assume fullyTypedIdent is a schema.
  526. Schema schema = resolveSchema(fullyTypedIdent);
  527. if (schema != null) {
  528. items.addTables(schema, null, lastPrefix, quoted, substitutionOffset);
  529. if (includeViews) {
  530. items.addViews(schema, null, lastPrefix, quoted, substitutionOffset);
  531. }
  532. }
  533. // Assume fullyTypedIdent is a catalog.
  534. Catalog catalog = resolveCatalog(fullyTypedIdent);
  535. if (catalog != null) {
  536. completeCatalog(catalog, lastPrefix, quoted);
  537. }
  538. }
  539. }
  540. /** Adds all tuples from default schema, all schemas from defaultcatalog
  541. * and all catalogs. */
  542. private void completeTupleSimpleIdent(String typedPrefix, boolean quoted) {
  543. Schema defaultSchema = metadata.getDefaultSchema();
  544. if (defaultSchema != null) {
  545. // All tuples in default schema.
  546. items.addTables(defaultSchema, null, typedPrefix, quoted, substitutionOffset);
  547. if (includeViews) {
  548. items.addViews(defaultSchema, null, typedPrefix, quoted, substitutionOffset);
  549. }
  550. }
  551. // All schemas.
  552. Catalog defaultCatalog = metadata.getDefaultCatalog();
  553. items.addSchemas(defaultCatalog, null, typedPrefix, quoted, substitutionOffset);
  554. // All catalogs.
  555. items.addCatalogs(metadata, null, typedPrefix, quoted, substitutionOffset);
  556. }
  557. /** Adds all tuples in schema get from fully qualified identifier or all
  558. * schemas from catalog. */
  559. private void completeTupleQualIdent(QualIdent fullyTypedIdent, String lastPrefix, boolean quoted) {
  560. Schema schema = resolveSchema(fullyTypedIdent);
  561. if (schema != null) {
  562. // tuples in the typed schema.
  563. items.addTables(schema, null, lastPrefix, quoted, substitutionOffset);
  564. if (includeViews) {
  565. items.addViews(schema, null, lastPrefix, quoted, substitutionOffset);
  566. }
  567. }
  568. Catalog catalog = resolveCatalog(fullyTypedIdent);
  569. if (catalog != null) {
  570. // Items in the typed catalog.
  571. completeCatalog(catalog, lastPrefix, quoted);
  572. }
  573. }
  574. private void completeSimpleIdentBasedOnFromClause(String typedPrefix, boolean quoted) {
  575. assert tablesClause != null;
  576. Set<QualIdent> tupleNames = tablesClause.getUnaliasedTableNames();
  577. Set<Tuple> tuples = resolveTuples(tupleNames);
  578. Set<QualIdent> allTupleNames = new TreeSet<QualIdent>(tupleNames);
  579. Set<Tuple> allTuples = new LinkedHashSet<Tuple>(tuples);
  580. Map<String, QualIdent> aliases = tablesClause.getAliasedTableNames();
  581. for (Entry<String, QualIdent> entry : aliases.entrySet()) {
  582. QualIdent tupleName = entry.getValue();
  583. allTupleNames.add(tupleName);
  584. Tuple tuple = resolveTuple(tupleName);
  585. if (tuple != null) {
  586. allTuples.add(tuple);
  587. }
  588. }
  589. // Aliases.
  590. Map<String, QualIdent> sortedAliases = new TreeMap<String, QualIdent>(aliases);
  591. items.addAliases(sortedAliases, typedPrefix, quoted, substitutionOffset);
  592. // Columns from aliased and non-aliased tuples in the FROM clause.
  593. for (Tuple tuple : allTuples) {
  594. items.addColumns(tuple, typedPrefix, quoted, substitutionOffset);
  595. }
  596. // Tuples from default schema, restricted to non-aliased tuple names in the FROM clause.
  597. Schema defaultSchema = metadata.getDefaultSchema();
  598. if (defaultSchema != null) {
  599. Set<String> simpleTupleNames = new HashSet<String>();
  600. for (Tuple tuple : tuples) {
  601. if (tuple.getParent().isDefault()) {
  602. simpleTupleNames.add(tuple.getName());
  603. }
  604. }
  605. items.addTables(defaultSchema, simpleTupleNames, typedPrefix, quoted, substitutionOffset);
  606. if (includeViews) {
  607. items.addViews(defaultSchema, simpleTupleNames, typedPrefix, quoted, substitutionOffset);
  608. }
  609. }
  610. // Schemas from default catalog other than the default schema, based on non-aliased tuple names in the FROM clause.
  611. // Catalogs based on non-aliased tuples names in the FROM clause.
  612. Set<String> schemaNames = new HashSet<String>();
  613. Set<String> catalogNames = new HashSet<String>();
  614. for (Tuple tuple : tuples) {
  615. Schema schema = tuple.getParent();
  616. Catalog catalog = schema.getParent();
  617. if (!schema.isDefault() && !schema.isSynthetic() && catalog.isDefault()) {
  618. schemaNames.add(schema.getName());
  619. }
  620. if (!catalog.isDefault()) {
  621. catalogNames.add(catalog.getName());
  622. }
  623. }
  624. Catalog defaultCatalog = metadata.getDefaultCatalog();
  625. items.addSchemas(defaultCatalog, schemaNames, typedPrefix, quoted, substitutionOffset);
  626. items.addCatalogs(metadata, catalogNames, typedPrefix, quoted, substitutionOffset);
  627. }
  628. private void completeQualIdentBasedOnFromClause(QualIdent fullyTypedIdent, String lastPrefix, boolean quoted) {
  629. assert tablesClause != null;
  630. Set<Tuple> tuples = resolveTuples(tablesClause.getUnaliasedTableNames());
  631. // Assume fullyTypedIdent is the name of a tuple in the default schema.
  632. Tuple foundTuple = resolveTuple(fullyTypedIdent);
  633. if (foundTuple == null || !tuples.contains(foundTuple)) {
  634. // Tuple not found, or it is not in the FROM clause.
  635. foundTuple = null;
  636. // Then assume fullyTypedIdent is an alias.
  637. if (fullyTypedIdent.isSimple()) {
  638. QualIdent aliasedTupleName = tablesClause.getTableNameByAlias(fullyTypedIdent.getSimpleName());
  639. if (aliasedTupleName != null) {
  640. foundTuple = resolveTuple(aliasedTupleName);
  641. }
  642. }
  643. }
  644. if (foundTuple != null) {
  645. items.addColumns(foundTuple, lastPrefix, quoted, substitutionOffset);
  646. }
  647. // Now assume fullyTypedIdent is the name of a schema in the default catalog.
  648. Schema schema = resolveSchema(fullyTypedIdent);
  649. if (schema != null) {
  650. Set<String> tupleNames = new HashSet<String>();
  651. for (Tuple tuple : tuples) {
  652. if (tuple.getParent().equals(schema)) {
  653. tupleNames.add(tuple.getName());
  654. }
  655. }
  656. items.addTables(schema, tupleNames, lastPrefix, quoted, substitutionOffset);
  657. if (includeViews) {
  658. items.addViews(schema, tupleNames, lastPrefix, quoted, substitutionOffset);
  659. }
  660. }
  661. // Now assume fullyTypedIdent is the name of a catalog.
  662. Catalog catalog = resolveCatalog(fullyTypedIdent);
  663. if (catalog != null) {
  664. Set<String> syntheticSchemaTupleNames = new HashSet<String>();
  665. Set<String> schemaNames = new HashSet<String>();
  666. for (Tuple tuple : tuples) {
  667. schema = tuple.getParent();
  668. if (schema.getParent().equals(catalog)) {
  669. if (!schema.isSynthetic()) {
  670. schemaNames.add(schema.getName());
  671. } else {
  672. syntheticSchemaTupleNames.add(tuple.getName());
  673. }
  674. }
  675. }
  676. items.addSchemas(catalog, schemaNames, lastPrefix, quoted, substitutionOffset);
  677. items.addTables(catalog.getSyntheticSchema(), syntheticSchemaTupleNames, lastPrefix, quoted, substitutionOffset);
  678. if (includeViews) {
  679. items.addViews(catalog.getSyntheticSchema(), syntheticSchemaTupleNames, lastPrefix, quoted, substitutionOffset);
  680. }
  681. }
  682. }
  683. private void completeCatalog(Catalog catalog, String prefix, boolean quoted) {
  684. items.addSchemas(catalog, null, prefix, quoted, substitutionOffset);
  685. Schema syntheticSchema = catalog.getSyntheticSchema();
  686. if (syntheticSchema != null) {
  687. items.addTables(syntheticSchema, null, prefix, quoted, substitutionOffset);
  688. if (includeViews) {
  689. items.addViews(syntheticSchema, null, prefix, quoted, substitutionOffset);
  690. }
  691. }
  692. }
  693. private Catalog resolveCatalog(QualIdent catalogName) {
  694. if (catalogName.isSimple()) {
  695. return metadata.getCatalog(catalogName.getSimpleName());
  696. }
  697. return null;
  698. }
  699. private Schema resolveSchema(QualIdent schemaName) {
  700. Schema schema = null;
  701. switch (schemaName.size()) {
  702. case 1:
  703. Catalog catalog = metadata.getDefaultCatalog();
  704. schema = catalog.getSchema(schemaName.getSimpleName());
  705. break;
  706. case 2:
  707. catalog = metadata.getCatalog(schemaName.getFirstQualifier());
  708. if (catalog != null) {
  709. schema = catalog.getSchema(schemaName.getSimpleName());
  710. }
  711. break;
  712. }
  713. return schema;
  714. }
  715. private Tuple resolveTuple(QualIdent tupleName) {
  716. if (tupleName == null) {
  717. return null;
  718. }
  719. Tuple tuple = null;
  720. switch (tupleName.size()) {
  721. case 1:
  722. Schema schema = metadata.getDefaultSchema();
  723. if (schema != null) {
  724. return getTuple(schema, tupleName);
  725. }
  726. break;
  727. case 2:
  728. Catalog catalog = metadata.getDefaultCatalog();
  729. schema = catalog.getSchema(tupleName.getFirstQualifier());
  730. if (schema != null) {
  731. tuple = getTuple(schema, tupleName);
  732. if (tuple != null) {
  733. return tuple;
  734. }
  735. }
  736. catalog = metadata.getCatalog(tupleName.getFirstQualifier());
  737. if (catalog != null) {
  738. schema = catalog.getSyntheticSchema();
  739. if (schema != null) {
  740. return getTuple(schema, tupleName);
  741. }
  742. }
  743. break;
  744. case 3:
  745. catalog = metadata.getCatalog(tupleName.getFirstQualifier());
  746. if (catalog != null) {
  747. schema = catalog.getSchema(tupleName.getSecondQualifier());
  748. if (schema != null) {
  749. return getTuple(schema, tupleName);
  750. }
  751. }
  752. break;
  753. }
  754. return null;
  755. }
  756. private Tuple getTuple(Schema schema, QualIdent tupleName) {
  757. Table table = schema.getTable(tupleName.getSimpleName());
  758. if (table != null) {
  759. return table;
  760. }
  761. if (includeViews) {
  762. View view = schema.getView(tupleName.getSimpleName());
  763. if (view != null) {
  764. return view;
  765. }
  766. }
  767. return null;
  768. }
  769. private Set<Tuple> resolveTuples(Set<QualIdent> tupleNames) {
  770. Set<Tuple> result = new LinkedHashSet<Tuple>(tupleNames.size());
  771. for (QualIdent tupleName : tupleNames) {
  772. Tuple tuple = resolveTuple(tupleName);
  773. if (tuple != null) {
  774. result.add(tuple);
  775. }
  776. }
  777. return result;
  778. }
  779. /** Returns part of token before cursor or entire token if at the end of it.
  780. * Returns null prefix if token is comma, rparen or whitespace. Returned offset is
  781. * caret offset if prefix is null, otherwise it is token offset. */
  782. private Symbol findPrefix() {
  783. TokenSequence<SQLTokenId> seq = env.getTokenSequence();
  784. int caretOffset = env.getCaretOffset();
  785. String prefix = null;
  786. if (seq.move(caretOffset) > 0) {
  787. // Not on token boundary.
  788. if (!seq.moveNext() && !seq.movePrevious()) {
  789. return new Symbol(null, caretOffset, caretOffset);
  790. }
  791. } else {
  792. if (!seq.movePrevious()) {
  793. return new Symbol(null, caretOffset, caretOffset);
  794. }
  795. }
  796. switch (seq.token().id()) {
  797. case WHITESPACE:
  798. case COMMA:
  799. case RPAREN:
  800. return new Symbol(null, caretOffset, caretOffset);
  801. default:
  802. int offset = caretOffset - seq.offset();
  803. if (offset > 0 && offset < seq.token().length()) {
  804. prefix = seq.token().text().subSequence(0, offset).toString();
  805. } else {
  806. prefix = seq.token().text().toString();
  807. }
  808. return new Symbol(prefix, seq.offset(), seq.offset());
  809. }
  810. }
  811. /** Finds valid identifier within SQL statement at cursor position.
  812. * It handles fully qualified and quoted identifiers. Returns null if no
  813. * valid identifier found. */
  814. private Identifier findIdentifier() {
  815. TokenSequence<SQLTokenId> seq = env.getTokenSequence();
  816. int caretOffset = env.getCaretOffset();
  817. final List<String> parts = new ArrayList<String>();
  818. if (seq.move(caretOffset) > 0) {
  819. // Not on token boundary.
  820. if (!seq.moveNext() && !seq.movePrevious()) {
  821. return null;
  822. }
  823. } else {
  824. if (!seq.movePrevious()) {
  825. return null;
  826. }
  827. }
  828. switch (seq.token().id()) {
  829. case LINE_COMMENT:
  830. case BLOCK_COMMENT:
  831. case INT_LITERAL:
  832. case DOUBLE_LITERAL:
  833. case STRING:
  834. case INCOMPLETE_STRING:
  835. case RPAREN: // not identifier (abcd)|
  836. return null;
  837. }
  838. boolean incomplete = false; // Whether incomplete, like '"foo.bar."|'.
  839. boolean wasDot = false; // Whether the previous token was a dot.
  840. int lastPrefixOffset = -1;
  841. main: do {
  842. switch (seq.token().id()) {
  843. case DOT:
  844. if (parts.isEmpty()) {
  845. lastPrefixOffset = caretOffset; // Not the dot offset,
  846. // since the user may have typed whitespace after the dot.
  847. incomplete = true;
  848. }
  849. wasDot = true;
  850. break;
  851. case IDENTIFIER:
  852. case INCOMPLETE_IDENTIFIER:
  853. case KEYWORD:
  854. if (wasDot || parts.isEmpty()) {
  855. if (parts.isEmpty() && lastPrefixOffset == -1) {
  856. lastPrefixOffset = seq.offset();
  857. }
  858. wasDot = false;
  859. String part;
  860. int offset = caretOffset - seq.offset();
  861. String tokenText = seq.token().text().toString();
  862. if (offset > 0 && offset < seq.token().length()) {
  863. String quoteString = quoter.getQuoteString();
  864. if (tokenText.startsWith(quoteString) && tokenText.endsWith(quoteString) && offset == tokenText.length() - 1) {
  865. // identifier inside closed quotes and cursor before ending quote ("foo|")
  866. // => completion will not add quotes and replace just foo
  867. part = tokenText.substring(1, offset);
  868. lastPrefixOffset++;
  869. } else {
  870. part = tokenText.substring(0, offset);
  871. }
  872. } else {
  873. part = tokenText;
  874. }
  875. parts.add(part);
  876. } else {
  877. // Two following identifiers.
  878. return null;
  879. }
  880. break;
  881. case WHITESPACE:
  882. case LINE_COMMENT:
  883. case BLOCK_COMMENT:
  884. if (seq.movePrevious()) {
  885. switch (seq.token().id()) {
  886. case IDENTIFIER: // Cannot complete 'SELECT foo |'.
  887. case INT_LITERAL: // Cannot complete 'WHERE a = 1 |'.
  888. case DOUBLE_LITERAL:
  889. case STRING:
  890. case INCOMPLETE_STRING:
  891. case RPAREN: // foo is not valid identifier in 'WHERE (a+b > c) foo'
  892. return null;
  893. case OPERATOR: // foo is not valid identifier in 'SELECT * foo'
  894. if (seq.token().text().toString().equals("*")) { //NOI18N
  895. if (seq.movePrevious()) {
  896. if (seq.movePrevious()) {
  897. if (seq.token().text().toString().equalsIgnoreCase("SELECT")) { //NOI18N
  898. return null;
  899. }
  900. seq.moveNext();
  901. }
  902. seq.moveNext();
  903. }
  904. }
  905. break;
  906. case DOT:
  907. // Process the dot in the main loop.
  908. seq.moveNext();
  909. continue main;
  910. }
  911. }
  912. break main;
  913. default:
  914. break main;
  915. }
  916. } while (seq.movePrevious());
  917. Collections.reverse(parts);
  918. return createIdentifier(parts, incomplete, lastPrefixOffset >= 0 ? lastPrefixOffset : caretOffset);
  919. }
  920. /**
  921. * @param lastPrefixOffset the offset of the last prefix in the identifier, or
  922. * if no such prefix, the caret offset.
  923. * @return
  924. */
  925. private Identifier createIdentifier(List<String> parts, boolean incomplete, int lastPrefixOffset) {
  926. String lastPrefix = null;
  927. boolean quoted = false;
  928. int substOffset = lastPrefixOffset;
  929. if (parts.isEmpty()) {
  930. if (incomplete) {
  931. // Just a dot was typed.
  932. return null;
  933. }
  934. // Fine, nothing was typed.
  935. } else {
  936. if (!incomplete) {
  937. lastPrefix = parts.remove(parts.size() - 1);
  938. String quoteString = quoter.getQuoteString();
  939. if (quoteString.length() > 0 && lastPrefix.startsWith(quoteString)) {
  940. if (lastPrefix.endsWith(quoteString) && lastPrefix.length() > quoteString.length()) {
  941. // User typed '"foo"."bar"|', can't complete that.
  942. return null;
  943. }
  944. int lastPrefixLength = lastPrefix.length();
  945. lastPrefix = quoter.unquote(lastPrefix);
  946. lastPrefixOffset = lastPrefixOffset + (lastPrefixLength - lastPrefix.length());
  947. quoted = true;
  948. } else if (quoteString.length() > 0 && lastPrefix.endsWith(quoteString)) {
  949. // User typed '"foo".bar"|', can't complete.
  950. return null;
  951. }
  952. }
  953. for (int i = 0; i < parts.size(); i++) {
  954. String unquoted = quoter.unquote(parts.get(i));
  955. if (unquoted.length() == 0) {
  956. // User typed something like '"foo".""."bar|'.
  957. return null;
  958. }
  959. parts.set(i, unquoted);
  960. }
  961. }
  962. return new Identifier(new QualIdent(parts), lastPrefix, quoted, lastPrefixOffset, substOffset);
  963. }
  964. private static void reportError(MetadataModelException e) {
  965. LOGGER.log(Level.INFO, null, e);
  966. String error = e.getMessage();
  967. String message;
  968. if (error != null) {
  969. message = NbBundle.getMessage(SQLCompletionQuery.class, "MSG_Error", error);
  970. } else {
  971. message = NbBundle.getMessage(SQLCompletionQuery.class, "MSG_ErrorNoMessage");
  972. }
  973. DialogDisplayer.getDefault().notifyLater(new NotifyDescriptor.Message(message, NotifyDescriptor.ERROR_MESSAGE));
  974. }
  975. private static class Symbol {
  976. final String lastPrefix;
  977. final int anchorOffset;
  978. final int substitutionOffset;
  979. private Symbol(String lastPrefix, int anchorOffset, int substitutionOffset) {
  980. this.lastPrefix = lastPrefix;
  981. this.anchorOffset = anchorOffset;
  982. this.substitutionOffset = substitutionOffset;
  983. }
  984. }
  985. private static final class Identifier extends Symbol {
  986. final QualIdent fullyTypedIdent;
  987. final boolean quoted;
  988. private Identifier(QualIdent fullyTypedIdent, String lastPrefix, boolean quoted, int anchorOffset, int substitutionOffset) {
  989. super(lastPrefix, anchorOffset, substitutionOffset);
  990. this.fullyTypedIdent = fullyTypedIdent;
  991. this.quoted = quoted;
  992. }
  993. }
  994. }