mirror of
				https://github.com/Fabio286/antares.git
				synced 2025-06-05 21:59:22 +02:00 
			
		
		
		
	feat: option to insert table rows
This commit is contained in:
		| @@ -44,4 +44,14 @@ export default (connections) => { | ||||
|          return { status: 'error', response: err.toString() }; | ||||
|       } | ||||
|    }); | ||||
|  | ||||
|    ipcMain.handle('insertTableRows', async (event, params) => { | ||||
|       try { | ||||
|          await Tables.insertTableRows(connections[params.uid], params); | ||||
|          return { status: 'success' }; | ||||
|       } | ||||
|       catch (err) { | ||||
|          return { status: 'error', response: err.toString() }; | ||||
|       } | ||||
|    }); | ||||
| }; | ||||
|   | ||||
| @@ -32,7 +32,7 @@ export class AntaresConnector { | ||||
|          limit: [], | ||||
|          join: [], | ||||
|          update: [], | ||||
|          insert: [], | ||||
|          insert: {}, | ||||
|          delete: false | ||||
|       }; | ||||
|       this._query = Object.assign({}, this._queryDefaults); | ||||
| @@ -108,6 +108,11 @@ export class AntaresConnector { | ||||
|       return this; | ||||
|    } | ||||
|  | ||||
|    into (table) { | ||||
|       this._query.from = table; | ||||
|       return this; | ||||
|    } | ||||
|  | ||||
|    delete (table) { | ||||
|       this._query.delete = true; | ||||
|       this.from(table); | ||||
| @@ -162,6 +167,16 @@ export class AntaresConnector { | ||||
|       return this; | ||||
|    } | ||||
|  | ||||
|    /** | ||||
|     * @param {Object} obj field: value | ||||
|     * @returns | ||||
|     * @memberof AntaresConnector | ||||
|     */ | ||||
|    insert (obj) { | ||||
|       this._query.insert = { ...this._query.insert, ...obj }; | ||||
|       return this; | ||||
|    } | ||||
|  | ||||
|    /** | ||||
|     * @returns {string} SQL string | ||||
|     * @memberof AntaresConnector | ||||
| @@ -188,8 +203,10 @@ export class AntaresConnector { | ||||
|  | ||||
|       // FROM | ||||
|       let fromRaw = ''; | ||||
|       if (!this._query.update.length && !!this._query.from) | ||||
|       if (!this._query.update.length && !Object.keys(this._query.insert).length && !!this._query.from) | ||||
|          fromRaw = 'FROM'; | ||||
|       else if (Object.keys(this._query.insert).length) | ||||
|          fromRaw = 'INTO'; | ||||
|  | ||||
|       switch (this._client) { | ||||
|          case 'maria': | ||||
| @@ -209,6 +226,21 @@ export class AntaresConnector { | ||||
|       const updateArray = this._query.update.reduce(this._reducer, []); | ||||
|       const updateRaw = updateArray.length ? `SET ${updateArray.join(', ')} ` : ''; | ||||
|  | ||||
|       let insertRaw = ''; | ||||
|       if (Object.keys(this._query.insert).length) { | ||||
|          const fieldsList = []; | ||||
|          const valueList = []; | ||||
|          const fields = this._query.insert; | ||||
|  | ||||
|          for (const key in fields) { | ||||
|             if (fields[key] === null) continue; | ||||
|             fieldsList.push(key); | ||||
|             valueList.push(typeof fields[key] === 'number' ? fields[key] : `"${fields[key]}"`); | ||||
|          } | ||||
|  | ||||
|          insertRaw = ` (${fieldsList.join(',')}) VALUES (${valueList.join(',')}) `; | ||||
|       } | ||||
|  | ||||
|       const groupByArray = this._query.groupBy.reduce(this._reducer, []); | ||||
|       const groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')} ` : ''; | ||||
|  | ||||
| @@ -229,7 +261,7 @@ export class AntaresConnector { | ||||
|             break; | ||||
|       } | ||||
|  | ||||
|       return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}`; | ||||
|       return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${insertRaw}`; | ||||
|    } | ||||
|  | ||||
|    /** | ||||
|   | ||||
| @@ -31,7 +31,10 @@ export default class { | ||||
|             datePrecision: field.DATETIME_PRECISION, | ||||
|             charLength: field.CHARACTER_MAXIMUM_LENGTH, | ||||
|             isNullable: field.IS_NULLABLE, | ||||
|             default: field.COLUMN_DEFAULT | ||||
|             default: field.COLUMN_DEFAULT, | ||||
|             charset: field.CHARACTER_SET_NAME, | ||||
|             collation: field.COLLATION_NAME, | ||||
|             autoIncrement: field.EXTRA.includes('auto_increment') | ||||
|          }; | ||||
|       }); | ||||
|    } | ||||
|   | ||||
| @@ -50,4 +50,14 @@ export default class { | ||||
|          .where({ [params.primary]: `IN (${params.rows.join(',')})` }) | ||||
|          .run(); | ||||
|    } | ||||
|  | ||||
|    static async insertTableRows (connection, params) { // Prepare every field like updateTableCell method | ||||
|       for (let i = 0; i < params.repeat; i++) { | ||||
|          await connection | ||||
|             .schema(params.schema) | ||||
|             .into(params.table) | ||||
|             .insert(params.row) | ||||
|             .run(); | ||||
|       } | ||||
|    } | ||||
| } | ||||
|   | ||||
							
								
								
									
										278
									
								
								src/renderer/components/ModalNewTableRow.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										278
									
								
								src/renderer/components/ModalNewTableRow.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,278 @@ | ||||
| <template> | ||||
|    <div class="modal active"> | ||||
|       <a class="modal-overlay" @click.stop="closeModal" /> | ||||
|       <div class="modal-container p-0"> | ||||
|          <div class="modal-header pl-2"> | ||||
|             <div class="modal-title h6"> | ||||
|                <div class="d-flex"> | ||||
|                   <i class="mdi mdi-24px mdi-playlist-plus mr-1" /> {{ $t('message.addNewRow') }} | ||||
|                </div> | ||||
|             </div> | ||||
|             <a class="btn btn-clear c-hand" @click.stop="closeModal" /> | ||||
|          </div> | ||||
|          <div class="modal-body"> | ||||
|             <div class="content"> | ||||
|                <form class="form-horizontal"> | ||||
|                   <fieldset :disabled="isInserting"> | ||||
|                      <div | ||||
|                         v-for="(field, key) in fields" | ||||
|                         :key="field.name" | ||||
|                         class="form-group" | ||||
|                      > | ||||
|                         <div class="col-4 col-sm-12"> | ||||
|                            <label class="form-label" :title="field.name">{{ field.name }}</label> | ||||
|                         </div> | ||||
|                         <div class="input-group col-8 col-sm-12"> | ||||
|                            <input | ||||
|                               v-if="inputProps(field).mask" | ||||
|                               v-model="localRow[field.name]" | ||||
|                               v-mask="inputProps(field).mask" | ||||
|                               class="form-input" | ||||
|                               :type="inputProps(field).type" | ||||
|                               :disabled="fieldsToExclude.includes(field.name)" | ||||
|                               :tabindex="key+1" | ||||
|                            > | ||||
|                            <input | ||||
|                               v-else | ||||
|                               v-model="localRow[field.name]" | ||||
|                               class="form-input" | ||||
|                               :type="inputProps(field).type" | ||||
|                               :disabled="fieldsToExclude.includes(field.name)" | ||||
|                               :tabindex="key+1" | ||||
|                            > | ||||
|                            <span class="input-group-addon" :class="`type-${field.type}`"> | ||||
|                               {{ field.type }} {{ fieldLength(field) | wrapNumber }} | ||||
|                            </span> | ||||
|                            <label class="form-checkbox ml-3" :title="$t('word.insert')"> | ||||
|                               <input | ||||
|                                  type="checkbox" | ||||
|                                  :checked="!field.autoIncrement" | ||||
|                                  @change.prevent="toggleFields($event, field)" | ||||
|                               ><i class="form-icon" /> | ||||
|                            </label> | ||||
|                         </div> | ||||
|                      </div> | ||||
|                   </fieldset> | ||||
|                </form> | ||||
|             </div> | ||||
|          </div> | ||||
|          <div class="modal-footer text-light"> | ||||
|             <div class="input-group col-3 tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')"> | ||||
|                <input | ||||
|                   v-model="nInserts" | ||||
|                   type="number" | ||||
|                   class="form-input" | ||||
|                   min="1" | ||||
|                   :disabled="isInserting" | ||||
|                > | ||||
|                <span class="input-group-addon"> | ||||
|                   <i class="mdi mdi-24px mdi-repeat" /> | ||||
|                </span> | ||||
|             </div> | ||||
|             <div> | ||||
|                <button | ||||
|                   class="btn btn-primary mr-2" | ||||
|                   :class="{'loading': isInserting}" | ||||
|                   @click.stop="insertRows" | ||||
|                > | ||||
|                   {{ $t('word.insert') }} | ||||
|                </button> | ||||
|                <button class="btn btn-link" @click.stop="closeModal"> | ||||
|                   {{ $t('word.close') }} | ||||
|                </button> | ||||
|             </div> | ||||
|          </div> | ||||
|       </div> | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import moment from 'moment'; | ||||
| import { TEXT, LONG_TEXT, NUMBER, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes'; | ||||
| import { mask } from 'vue-the-mask'; | ||||
| import { mapGetters, mapActions } from 'vuex'; | ||||
| import Tables from '@/ipc-api/Tables'; | ||||
|  | ||||
| export default { | ||||
|    name: 'ModalNewTableRow', | ||||
|    directives: { | ||||
|       mask | ||||
|    }, | ||||
|    filters: { | ||||
|       wrapNumber (num) { | ||||
|          if (!num) return ''; | ||||
|          return `(${num})`; | ||||
|       } | ||||
|    }, | ||||
|    props: { | ||||
|       fields: Array, | ||||
|       connection: Object | ||||
|    }, | ||||
|    data () { | ||||
|       return { | ||||
|          localRow: {}, | ||||
|          fieldsToExclude: [], | ||||
|          nInserts: 1, | ||||
|          isInserting: false | ||||
|       }; | ||||
|    }, | ||||
|    computed: { | ||||
|       ...mapGetters({ | ||||
|          getWorkspace: 'workspaces/getWorkspace' | ||||
|       }), | ||||
|       workspace () { | ||||
|          return this.getWorkspace(this.connection.uid); | ||||
|       } | ||||
|    }, | ||||
|    watch: { | ||||
|       nInserts (val) { | ||||
|          if (!val || val < 1) | ||||
|             this.nInserts = 1; | ||||
|       } | ||||
|    }, | ||||
|    mounted () { | ||||
|       const rowObj = {}; | ||||
|  | ||||
|       for (const field of this.fields) { | ||||
|          let fieldDefault; | ||||
|  | ||||
|          if (field.default === 'NULL') fieldDefault = null; | ||||
|          else { | ||||
|             if (NUMBER.includes(field.type)) | ||||
|                fieldDefault = +field.default; | ||||
|  | ||||
|             if ([...TEXT, ...LONG_TEXT].includes(field.type)) | ||||
|                fieldDefault = field.default ? field.default.substring(1, field.default.length - 1) : ''; | ||||
|  | ||||
|             if ([...TIME, ...DATE].includes(field.type)) | ||||
|                fieldDefault = field.default; | ||||
|  | ||||
|             if (DATETIME.includes(field.type)) { | ||||
|                if (field.default && field.default.includes('current_timestamp')) { | ||||
|                   let datePrecision = ''; | ||||
|                   for (let i = 0; i < field.datePrecision; i++) | ||||
|                      datePrecision += i === 0 ? '.S' : 'S'; | ||||
|                   fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`); | ||||
|                } | ||||
|             } | ||||
|          } | ||||
|  | ||||
|          rowObj[field.name] = fieldDefault; | ||||
|  | ||||
|          if (field.autoIncrement)// Disable by default auto increment fields | ||||
|             this.fieldsToExclude = [...this.fieldsToExclude, field.name]; | ||||
|       } | ||||
|  | ||||
|       this.localRow = { ...rowObj }; | ||||
|    }, | ||||
|    methods: { | ||||
|       ...mapActions({ | ||||
|          addNotification: 'notifications/addNotification' | ||||
|       }), | ||||
|       async insertRows () { | ||||
|          this.isInserting = true; | ||||
|          const rowToInsert = this.localRow; | ||||
|          Object.keys(rowToInsert).forEach(key => { | ||||
|             if (this.fieldsToExclude.includes(key)) | ||||
|                delete rowToInsert[key]; | ||||
|          }); | ||||
|  | ||||
|          try { | ||||
|             const { status, response } = await Tables.insertTableRows({ | ||||
|                uid: this.connection.uid, | ||||
|                schema: this.workspace.breadcrumbs.schema, | ||||
|                table: this.workspace.breadcrumbs.table, | ||||
|                row: rowToInsert, | ||||
|                repeat: this.nInserts | ||||
|             }); | ||||
|  | ||||
|             if (status === 'success') { | ||||
|                this.closeModal(); | ||||
|                this.$emit('reload'); | ||||
|             } | ||||
|             else | ||||
|                this.addNotification({ status: 'error', message: response }); | ||||
|          } | ||||
|          catch (err) { | ||||
|             this.addNotification({ status: 'error', message: err.stack }); | ||||
|          } | ||||
|  | ||||
|          this.isInserting = false; | ||||
|       }, | ||||
|       closeModal () { | ||||
|          this.$emit('hide'); | ||||
|       }, | ||||
|       fieldLength (field) { | ||||
|          if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null; | ||||
|          return field.numPrecision || field.datePrecision || field.charLength || 0; | ||||
|       }, | ||||
|       inputProps (field) { | ||||
|          if ([...TEXT, ...LONG_TEXT].includes(field.type)) | ||||
|             return { type: 'text', mask: false }; | ||||
|  | ||||
|          if (NUMBER.includes(field.type)) | ||||
|             return { type: 'number', mask: false }; | ||||
|  | ||||
|          if (TIME.includes(field.type)) { | ||||
|             let timeMask = '##:##:##'; | ||||
|             const precision = this.fieldLength(field); | ||||
|  | ||||
|             for (let i = 0; i < precision; i++) | ||||
|                timeMask += i === 0 ? '.#' : '#'; | ||||
|  | ||||
|             return { type: 'text', mask: timeMask }; | ||||
|          } | ||||
|  | ||||
|          if (DATE.includes(field.type)) | ||||
|             return { type: 'text', mask: '####-##-##' }; | ||||
|  | ||||
|          if (DATETIME.includes(field.type)) { | ||||
|             let datetimeMask = '####-##-## ##:##:##'; | ||||
|             const precision = this.fieldLength(field); | ||||
|  | ||||
|             for (let i = 0; i < precision; i++) | ||||
|                datetimeMask += i === 0 ? '.#' : '#'; | ||||
|  | ||||
|             return { type: 'text', mask: datetimeMask }; | ||||
|          } | ||||
|  | ||||
|          if (BLOB.includes(field.type)) | ||||
|             return { type: 'file', mask: false }; | ||||
|  | ||||
|          if (BIT.includes(field.type)) | ||||
|             return { type: 'text', mask: false }; | ||||
|  | ||||
|          return { type: 'text', mask: false }; | ||||
|       }, | ||||
|       toggleFields (event, field) { | ||||
|          if (event.target.checked) | ||||
|             this.fieldsToExclude = this.fieldsToExclude.filter(f => f !== field.name); | ||||
|          else | ||||
|             this.fieldsToExclude = [...this.fieldsToExclude, field.name]; | ||||
|       } | ||||
|    } | ||||
| }; | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
|   .modal-container { | ||||
|     max-width: 500px; | ||||
|   } | ||||
|  | ||||
|   .form-label { | ||||
|     overflow: hidden; | ||||
|     white-space: normal; | ||||
|     text-overflow: ellipsis; | ||||
|   } | ||||
|  | ||||
|   .input-group-addon { | ||||
|     display: flex; | ||||
|     align-items: center; | ||||
|   } | ||||
|  | ||||
|   .modal-footer { | ||||
|     display: flex; | ||||
|     justify-content: space-between; | ||||
|     align-items: center; | ||||
|   } | ||||
| </style> | ||||
| @@ -40,19 +40,28 @@ | ||||
|             @deleteSelected="deleteSelected" | ||||
|          /> | ||||
|       </div> | ||||
|       <ModalNewTableRow | ||||
|          v-if="isAddModal" | ||||
|          :fields="fields" | ||||
|          :connection="connection" | ||||
|          @hide="hideAddModal" | ||||
|          @reload="reloadTable" | ||||
|       /> | ||||
|    </div> | ||||
| </template> | ||||
|  | ||||
| <script> | ||||
| import Tables from '@/ipc-api/Tables'; | ||||
| import WorkspaceQueryTable from '@/components/WorkspaceQueryTable'; | ||||
| import ModalNewTableRow from '@/components/ModalNewTableRow'; | ||||
| import { mapGetters, mapActions } from 'vuex'; | ||||
| import tableTabs from '@/mixins/tableTabs'; | ||||
|  | ||||
| export default { | ||||
|    name: 'WorkspaceTableTab', | ||||
|    components: { | ||||
|       WorkspaceQueryTable | ||||
|       WorkspaceQueryTable, | ||||
|       ModalNewTableRow | ||||
|    }, | ||||
|    mixins: [tableTabs], | ||||
|    props: { | ||||
|   | ||||
| @@ -37,7 +37,8 @@ module.exports = { | ||||
|       download: 'Download', | ||||
|       add: 'Add', | ||||
|       data: 'Data', | ||||
|       properties: 'Properties' | ||||
|       properties: 'Properties', | ||||
|       insert: 'Insert' | ||||
|    }, | ||||
|    message: { | ||||
|       appWelcome: 'Welcome to Antares SQL Client!', | ||||
| @@ -65,7 +66,9 @@ module.exports = { | ||||
|       deleteRows: 'Delete row | Delete {count} rows', | ||||
|       confirmToDeleteRows: 'Do you confirm to delete one row? | Do you confirm to delete {count} rows?', | ||||
|       notificationsTimeout: 'Notifications timeout', | ||||
|       uploadFile: 'Upload file' | ||||
|       uploadFile: 'Upload file', | ||||
|       addNewRow: 'Add new row', | ||||
|       numberOfInserts: 'Number of inserts' | ||||
|    }, | ||||
|    // Date and Time | ||||
|    short: { | ||||
|   | ||||
| @@ -17,4 +17,8 @@ export default class { | ||||
|    static deleteTableRows (params) { | ||||
|       return ipcRenderer.invoke('deleteTableRows', params); | ||||
|    } | ||||
|  | ||||
|    static insertTableRows (params) { | ||||
|       return ipcRenderer.invoke('insertTableRows', params); | ||||
|    } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user