diff --git a/src/components/Schema.vue b/src/components/Schema.vue index 316ae9b..34da10f 100644 --- a/src/components/Schema.vue +++ b/src/components/Schema.vue @@ -31,9 +31,9 @@
diff --git a/src/components/SqlEditor.vue b/src/components/SqlEditor.vue index 65e87ea..a0d60d9 100644 --- a/src/components/SqlEditor.vue +++ b/src/components/SqlEditor.vue @@ -17,11 +17,11 @@ import { debounce } from 'debounce' const sqlHint = CM.hint.sql CM.hint.sql = (cm, options) => { - const token = cm.getTokenAt(cm.getCursor()).string + const token = cm.getTokenAt(cm.getCursor()).string.toUpperCase() const result = sqlHint(cm, options) // Don't show the hint if there is only one option // and the token is already completed with this option - if (result.list.length === 1 && result.list[0].text === token) { + if (result.list.length === 1 && result.list[0].text.toUpperCase() === token) { result.list = [] } return result @@ -46,13 +46,24 @@ export default { } } }, + computed: { + tables () { + const tables = {} + if (this.$store.state.schema) { + this.$store.state.schema.forEach(table => { + tables[table.name] = table.columns.map(column => column.name) + }) + } + return tables + } + }, watch: { query () { this.$emit('input', this.query) } }, methods: { - onCmChange: debounce((editor) => { + onCmChange: debounce(function (editor) { // Don't show autocomplete after a space or semicolon or in string literals const ch = editor.getTokenAt(editor.getCursor()).string.slice(-1) const tokenType = editor.getTokenAt(editor.getCursor()).type @@ -61,7 +72,7 @@ export default { } const hintOptions = { - // tables: {table: [col1, col2], table2: [colA, colB]}, + tables: this.tables, completeSingle: false, completeOnSingleClick: true, alignWithWord: false diff --git a/src/components/TableDescription.vue b/src/components/TableDescription.vue index 755f433..2b5ac7a 100644 --- a/src/components/TableDescription.vue +++ b/src/components/TableDescription.vue @@ -27,51 +27,14 @@ diff --git a/src/store/index.js b/src/store/index.js index f20ddf5..8c5ca71 100644 --- a/src/store/index.js +++ b/src/store/index.js @@ -1,8 +1,46 @@ import Vue from 'vue' import Vuex from 'vuex' +import sqliteParser from 'sqlite-parser' Vue.use(Vuex) +function getAst (sql) { + // There is a bug is sqlite-parser + // It throws an error if tokenizer has an arguments: + // https://github.com/codeschool/sqlite-parser/issues/59 + const fixedSql = sql + .replace(/(?<=tokenize=.+)"tokenchars=.+"/, '') + .replace(/(?<=tokenize=.+)"remove_diacritics=.+"/, '') + .replace(/(?<=tokenize=.+)"separators=.+"/, '') + .replace(/tokenize=.+(?=(,|\)))/, 'tokenize=unicode61') + + return sqliteParser(fixedSql) +} + +function getColumns (sql) { + const columns = [] + const ast = getAst(sql) + + const columnDefinition = ast.statement[0].format === 'table' + ? ast.statement[0].definition + : ast.statement[0].result.args.expression // virtual table + + columnDefinition.forEach(item => { + if (item.variant === 'column' && ['identifier', 'definition'].includes(item.type)) { + let type = item.datatype ? item.datatype.variant : 'N/A' + if (item.datatype && item.datatype.args) { + type = type + '(' + item.datatype.args.expression[0].value + if (item.datatype.args.expression.length === 2) { + type = type + ', ' + item.datatype.args.expression[1].value + } + type = type + ')' + } + columns.push({ name: item.name, type: type }) + } + }) + return columns +} + export default new Vuex.Store({ state: { schema: null, @@ -16,7 +54,14 @@ export default new Vuex.Store({ }, mutations: { saveSchema (state, schema) { - state.schema = schema + const parsedSchema = [] + schema.forEach(item => { + parsedSchema.push({ + name: item[0], + columns: getColumns(item[1]) + }) + }) + state.schema = parsedSchema }, saveDbFile (state, file) { state.dbFile = file