mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 18:18:53 +08:00
Add schema tokens in codemirror autocomplit #1
This commit is contained in:
@@ -31,9 +31,9 @@
|
|||||||
<div v-if="schemaVisible" class="schema">
|
<div v-if="schemaVisible" class="schema">
|
||||||
<table-description
|
<table-description
|
||||||
v-for="table in schema"
|
v-for="table in schema"
|
||||||
:key="table[0]"
|
:key="table.name"
|
||||||
:name="table[0]"
|
:name="table.name"
|
||||||
:sql="table[1]"
|
:columns="table.columns"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,11 +17,11 @@ import { debounce } from 'debounce'
|
|||||||
|
|
||||||
const sqlHint = CM.hint.sql
|
const sqlHint = CM.hint.sql
|
||||||
CM.hint.sql = (cm, options) => {
|
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)
|
const result = sqlHint(cm, options)
|
||||||
// Don't show the hint if there is only one option
|
// Don't show the hint if there is only one option
|
||||||
// and the token is already completed with this 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 = []
|
result.list = []
|
||||||
}
|
}
|
||||||
return result
|
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: {
|
watch: {
|
||||||
query () {
|
query () {
|
||||||
this.$emit('input', this.query)
|
this.$emit('input', this.query)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onCmChange: debounce((editor) => {
|
onCmChange: debounce(function (editor) {
|
||||||
// Don't show autocomplete after a space or semicolon or in string literals
|
// Don't show autocomplete after a space or semicolon or in string literals
|
||||||
const ch = editor.getTokenAt(editor.getCursor()).string.slice(-1)
|
const ch = editor.getTokenAt(editor.getCursor()).string.slice(-1)
|
||||||
const tokenType = editor.getTokenAt(editor.getCursor()).type
|
const tokenType = editor.getTokenAt(editor.getCursor()).type
|
||||||
@@ -61,7 +72,7 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hintOptions = {
|
const hintOptions = {
|
||||||
// tables: {table: [col1, col2], table2: [colA, colB]},
|
tables: this.tables,
|
||||||
completeSingle: false,
|
completeSingle: false,
|
||||||
completeOnSingleClick: true,
|
completeOnSingleClick: true,
|
||||||
alignWithWord: false
|
alignWithWord: false
|
||||||
|
|||||||
@@ -27,51 +27,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import sqliteParser from 'sqlite-parser'
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TableDescription',
|
name: 'TableDescription',
|
||||||
props: ['name', 'sql'],
|
props: ['name', 'columns'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
colVisible: false
|
colVisible: false
|
||||||
}
|
}
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
ast () {
|
|
||||||
// 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 = this.sql
|
|
||||||
.replace(/(?<=tokenize=.+)"tokenchars=.+"/, '')
|
|
||||||
.replace(/(?<=tokenize=.+)"remove_diacritics=.+"/, '')
|
|
||||||
.replace(/(?<=tokenize=.+)"separators=.+"/, '')
|
|
||||||
.replace(/tokenize=.+(?=(,|\)))/, 'tokenize=unicode61')
|
|
||||||
|
|
||||||
return sqliteParser(fixedSql)
|
|
||||||
},
|
|
||||||
columns () {
|
|
||||||
const columns = []
|
|
||||||
|
|
||||||
const columnDefinition = this.ast.statement[0].format === 'table'
|
|
||||||
? this.ast.statement[0].definition
|
|
||||||
: this.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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,8 +1,46 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import Vuex from 'vuex'
|
import Vuex from 'vuex'
|
||||||
|
import sqliteParser from 'sqlite-parser'
|
||||||
|
|
||||||
Vue.use(Vuex)
|
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({
|
export default new Vuex.Store({
|
||||||
state: {
|
state: {
|
||||||
schema: null,
|
schema: null,
|
||||||
@@ -16,7 +54,14 @@ export default new Vuex.Store({
|
|||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
saveSchema (state, schema) {
|
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) {
|
saveDbFile (state, file) {
|
||||||
state.dbFile = file
|
state.dbFile = file
|
||||||
|
|||||||
Reference in New Issue
Block a user