9 |
9 |
10 from PyQt6.QtCore import pyqtSignal, QVariant, Qt, pyqtSlot |
10 from PyQt6.QtCore import pyqtSignal, QVariant, Qt, pyqtSlot |
11 from PyQt6.QtGui import QStandardItemModel |
11 from PyQt6.QtGui import QStandardItemModel |
12 from PyQt6.QtWidgets import QWidget, QDialog, QAbstractItemView |
12 from PyQt6.QtWidgets import QWidget, QDialog, QAbstractItemView |
13 from PyQt6.QtSql import ( |
13 from PyQt6.QtSql import ( |
14 QSqlDatabase, QSqlError, QSqlTableModel, QSqlQueryModel, QSqlQuery |
14 QSqlDatabase, |
|
15 QSqlError, |
|
16 QSqlTableModel, |
|
17 QSqlQueryModel, |
|
18 QSqlQuery, |
15 ) |
19 ) |
16 |
20 |
17 from EricWidgets import EricMessageBox |
21 from EricWidgets import EricMessageBox |
18 |
22 |
19 from .Ui_SqlBrowserWidget import Ui_SqlBrowserWidget |
23 from .Ui_SqlBrowserWidget import Ui_SqlBrowserWidget |
20 |
24 |
21 |
25 |
22 class SqlBrowserWidget(QWidget, Ui_SqlBrowserWidget): |
26 class SqlBrowserWidget(QWidget, Ui_SqlBrowserWidget): |
23 """ |
27 """ |
24 Class implementing the SQL Browser widget. |
28 Class implementing the SQL Browser widget. |
25 |
29 |
26 @signal statusMessage(str) emitted to show a status message |
30 @signal statusMessage(str) emitted to show a status message |
27 """ |
31 """ |
|
32 |
28 statusMessage = pyqtSignal(str) |
33 statusMessage = pyqtSignal(str) |
29 |
34 |
30 cCount = 0 |
35 cCount = 0 |
31 |
36 |
32 def __init__(self, parent=None): |
37 def __init__(self, parent=None): |
33 """ |
38 """ |
34 Constructor |
39 Constructor |
35 |
40 |
36 @param parent reference to the parent widget (QWidget) |
41 @param parent reference to the parent widget (QWidget) |
37 """ |
42 """ |
38 super().__init__(parent) |
43 super().__init__(parent) |
39 self.setupUi(self) |
44 self.setupUi(self) |
40 |
45 |
41 self.table.addAction(self.insertRowAction) |
46 self.table.addAction(self.insertRowAction) |
42 self.table.addAction(self.deleteRowAction) |
47 self.table.addAction(self.deleteRowAction) |
43 |
48 |
44 if len(QSqlDatabase.drivers()) == 0: |
49 if len(QSqlDatabase.drivers()) == 0: |
45 EricMessageBox.information( |
50 EricMessageBox.information( |
46 self, |
51 self, |
47 self.tr("No database drivers found"), |
52 self.tr("No database drivers found"), |
48 self.tr( |
53 self.tr( |
49 """This tool requires at least one Qt database driver. """ |
54 """This tool requires at least one Qt database driver. """ |
50 """Please check the Qt documentation how to build the """ |
55 """Please check the Qt documentation how to build the """ |
51 """Qt SQL plugins.""")) |
56 """Qt SQL plugins.""" |
52 |
57 ), |
53 self.connections.tableActivated.connect( |
58 ) |
54 self.on_connections_tableActivated) |
59 |
55 self.connections.schemaRequested.connect( |
60 self.connections.tableActivated.connect(self.on_connections_tableActivated) |
56 self.on_connections_schemaRequested) |
61 self.connections.schemaRequested.connect(self.on_connections_schemaRequested) |
57 self.connections.cleared.connect(self.on_connections_cleared) |
62 self.connections.cleared.connect(self.on_connections_cleared) |
58 |
63 |
59 self.statusMessage.emit(self.tr("Ready")) |
64 self.statusMessage.emit(self.tr("Ready")) |
60 |
65 |
61 @pyqtSlot() |
66 @pyqtSlot() |
62 def on_clearButton_clicked(self): |
67 def on_clearButton_clicked(self): |
63 """ |
68 """ |
64 Private slot to clear the SQL entry widget. |
69 Private slot to clear the SQL entry widget. |
65 """ |
70 """ |
66 self.sqlEdit.clear() |
71 self.sqlEdit.clear() |
67 self.sqlEdit.setFocus() |
72 self.sqlEdit.setFocus() |
68 |
73 |
69 @pyqtSlot() |
74 @pyqtSlot() |
70 def on_executeButton_clicked(self): |
75 def on_executeButton_clicked(self): |
71 """ |
76 """ |
72 Private slot to execute the entered SQL query. |
77 Private slot to execute the entered SQL query. |
73 """ |
78 """ |
74 self.executeQuery() |
79 self.executeQuery() |
75 self.sqlEdit.setFocus() |
80 self.sqlEdit.setFocus() |
76 |
81 |
77 @pyqtSlot() |
82 @pyqtSlot() |
78 def on_insertRowAction_triggered(self): |
83 def on_insertRowAction_triggered(self): |
79 """ |
84 """ |
80 Private slot handling the action to insert a new row. |
85 Private slot handling the action to insert a new row. |
81 """ |
86 """ |
82 self.__insertRow() |
87 self.__insertRow() |
83 |
88 |
84 @pyqtSlot() |
89 @pyqtSlot() |
85 def on_deleteRowAction_triggered(self): |
90 def on_deleteRowAction_triggered(self): |
86 """ |
91 """ |
87 Private slot handling the action to delete a row. |
92 Private slot handling the action to delete a row. |
88 """ |
93 """ |
89 self.__deleteRow() |
94 self.__deleteRow() |
90 |
95 |
91 @pyqtSlot(str) |
96 @pyqtSlot(str) |
92 def on_connections_tableActivated(self, table): |
97 def on_connections_tableActivated(self, table): |
93 """ |
98 """ |
94 Private slot to show the contents of a table. |
99 Private slot to show the contents of a table. |
95 |
100 |
96 @param table name of the table for which to show the contents (string) |
101 @param table name of the table for which to show the contents (string) |
97 """ |
102 """ |
98 self.showTable(table) |
103 self.showTable(table) |
99 |
104 |
100 @pyqtSlot(str) |
105 @pyqtSlot(str) |
101 def on_connections_schemaRequested(self, table): |
106 def on_connections_schemaRequested(self, table): |
102 """ |
107 """ |
103 Private slot to show the schema of a table. |
108 Private slot to show the schema of a table. |
104 |
109 |
105 @param table name of the table for which to show the schema (string) |
110 @param table name of the table for which to show the schema (string) |
106 """ |
111 """ |
107 self.showSchema(table) |
112 self.showSchema(table) |
108 |
113 |
109 @pyqtSlot() |
114 @pyqtSlot() |
110 def on_connections_cleared(self): |
115 def on_connections_cleared(self): |
111 """ |
116 """ |
112 Private slot to clear the table. |
117 Private slot to clear the table. |
113 """ |
118 """ |
114 model = QStandardItemModel(self.table) |
119 model = QStandardItemModel(self.table) |
115 self.table.setModel(model) |
120 self.table.setModel(model) |
116 self.table.setEditTriggers( |
121 self.table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) |
117 QAbstractItemView.EditTrigger.NoEditTriggers) |
122 |
118 |
123 self.updateActions() |
119 self.updateActions() |
124 |
120 |
|
121 def addConnection(self, driver, dbName, user, password, host, port): |
125 def addConnection(self, driver, dbName, user, password, host, port): |
122 """ |
126 """ |
123 Public method to add a database connection. |
127 Public method to add a database connection. |
124 |
128 |
125 @param driver name of the Qt database driver (string) |
129 @param driver name of the Qt database driver (string) |
126 @param dbName name of the database (string) |
130 @param dbName name of the database (string) |
127 @param user user name (string) |
131 @param user user name (string) |
128 @param password password (string) |
132 @param password password (string) |
129 @param host host name (string) |
133 @param host host name (string) |
130 @param port port number (integer) |
134 @param port port number (integer) |
131 @return SQL error object (QSqlError) |
135 @return SQL error object (QSqlError) |
132 """ |
136 """ |
133 err = QSqlError() |
137 err = QSqlError() |
134 |
138 |
135 self.__class__.cCount += 1 |
139 self.__class__.cCount += 1 |
136 db = QSqlDatabase.addDatabase( |
140 db = QSqlDatabase.addDatabase( |
137 driver.upper(), "Browser{0:d}".format(self.__class__.cCount)) |
141 driver.upper(), "Browser{0:d}".format(self.__class__.cCount) |
|
142 ) |
138 db.setDatabaseName(dbName) |
143 db.setDatabaseName(dbName) |
139 db.setHostName(host) |
144 db.setHostName(host) |
140 db.setPort(port) |
145 db.setPort(port) |
141 if not db.open(user, password): |
146 if not db.open(user, password): |
142 err = db.lastError() |
147 err = db.lastError() |
143 db = QSqlDatabase() |
148 db = QSqlDatabase() |
144 QSqlDatabase.removeDatabase( |
149 QSqlDatabase.removeDatabase("Browser{0:d}".format(self.__class__.cCount)) |
145 "Browser{0:d}".format(self.__class__.cCount)) |
150 |
146 |
|
147 self.connections.refresh() |
151 self.connections.refresh() |
148 |
152 |
149 return err |
153 return err |
150 |
154 |
151 def addConnectionByDialog(self): |
155 def addConnectionByDialog(self): |
152 """ |
156 """ |
153 Public slot to add a database connection via an input dialog. |
157 Public slot to add a database connection via an input dialog. |
154 """ |
158 """ |
155 from .SqlConnectionDialog import SqlConnectionDialog |
159 from .SqlConnectionDialog import SqlConnectionDialog |
|
160 |
156 dlg = SqlConnectionDialog(self) |
161 dlg = SqlConnectionDialog(self) |
157 if dlg.exec() == QDialog.DialogCode.Accepted: |
162 if dlg.exec() == QDialog.DialogCode.Accepted: |
158 driver, dbName, user, password, host, port = dlg.getData() |
163 driver, dbName, user, password, host, port = dlg.getData() |
159 err = self.addConnection( |
164 err = self.addConnection(driver, dbName, user, password, host, port) |
160 driver, dbName, user, password, host, port) |
165 |
161 |
|
162 if err.type() != QSqlError.ErrorType.NoError: |
166 if err.type() != QSqlError.ErrorType.NoError: |
163 EricMessageBox.warning( |
167 EricMessageBox.warning( |
164 self, |
168 self, |
165 self.tr("Unable to open database"), |
169 self.tr("Unable to open database"), |
166 self.tr( |
170 self.tr("""An error occurred while opening the connection."""), |
167 """An error occurred while opening the connection.""")) |
171 ) |
168 |
172 |
169 def showTable(self, table): |
173 def showTable(self, table): |
170 """ |
174 """ |
171 Public slot to show the contents of a table. |
175 Public slot to show the contents of a table. |
172 |
176 |
173 @param table name of the table to be shown (string) |
177 @param table name of the table to be shown (string) |
174 """ |
178 """ |
175 model = QSqlTableModel(self.table, self.connections.currentDatabase()) |
179 model = QSqlTableModel(self.table, self.connections.currentDatabase()) |
176 model.setEditStrategy(QSqlTableModel.EditStrategy.OnRowChange) |
180 model.setEditStrategy(QSqlTableModel.EditStrategy.OnRowChange) |
177 model.setTable(table) |
181 model.setTable(table) |
178 model.select() |
182 model.select() |
179 if model.lastError().type() != QSqlError.ErrorType.NoError: |
183 if model.lastError().type() != QSqlError.ErrorType.NoError: |
180 self.statusMessage.emit(model.lastError().text()) |
184 self.statusMessage.emit(model.lastError().text()) |
181 self.table.setModel(model) |
185 self.table.setModel(model) |
182 self.table.setEditTriggers( |
186 self.table.setEditTriggers( |
183 QAbstractItemView.EditTrigger.DoubleClicked | |
187 QAbstractItemView.EditTrigger.DoubleClicked |
184 QAbstractItemView.EditTrigger.EditKeyPressed) |
188 | QAbstractItemView.EditTrigger.EditKeyPressed |
185 |
189 ) |
|
190 |
186 self.table.resizeColumnsToContents() |
191 self.table.resizeColumnsToContents() |
187 |
192 |
188 self.table.selectionModel().currentRowChanged.connect( |
193 self.table.selectionModel().currentRowChanged.connect(self.updateActions) |
189 self.updateActions) |
194 |
190 |
195 self.updateActions() |
191 self.updateActions() |
196 |
192 |
|
193 def showSchema(self, table): |
197 def showSchema(self, table): |
194 """ |
198 """ |
195 Public slot to show the schema of a table. |
199 Public slot to show the schema of a table. |
196 |
200 |
197 @param table name of the table to be shown (string) |
201 @param table name of the table to be shown (string) |
198 """ |
202 """ |
199 rec = self.connections.currentDatabase().record(table) |
203 rec = self.connections.currentDatabase().record(table) |
200 model = QStandardItemModel(self.table) |
204 model = QStandardItemModel(self.table) |
201 |
205 |
202 model.insertRows(0, rec.count()) |
206 model.insertRows(0, rec.count()) |
203 model.insertColumns(0, 7) |
207 model.insertColumns(0, 7) |
204 |
208 |
205 model.setHeaderData(0, Qt.Orientation.Horizontal, "Fieldname") |
209 model.setHeaderData(0, Qt.Orientation.Horizontal, "Fieldname") |
206 model.setHeaderData(1, Qt.Orientation.Horizontal, "Type") |
210 model.setHeaderData(1, Qt.Orientation.Horizontal, "Type") |
207 model.setHeaderData(2, Qt.Orientation.Horizontal, "Length") |
211 model.setHeaderData(2, Qt.Orientation.Horizontal, "Length") |
208 model.setHeaderData(3, Qt.Orientation.Horizontal, "Precision") |
212 model.setHeaderData(3, Qt.Orientation.Horizontal, "Precision") |
209 model.setHeaderData(4, Qt.Orientation.Horizontal, "Required") |
213 model.setHeaderData(4, Qt.Orientation.Horizontal, "Required") |
210 model.setHeaderData(5, Qt.Orientation.Horizontal, "Auto Value") |
214 model.setHeaderData(5, Qt.Orientation.Horizontal, "Auto Value") |
211 model.setHeaderData(6, Qt.Orientation.Horizontal, "Default Value") |
215 model.setHeaderData(6, Qt.Orientation.Horizontal, "Default Value") |
212 |
216 |
213 for i in range(rec.count()): |
217 for i in range(rec.count()): |
214 fld = rec.field(i) |
218 fld = rec.field(i) |
215 model.setData(model.index(i, 0), fld.name()) |
219 model.setData(model.index(i, 0), fld.name()) |
216 if fld.typeID() == -1: |
220 if fld.typeID() == -1: |
217 model.setData(model.index(i, 1), |
221 model.setData(model.index(i, 1), QVariant.typeToName(fld.type())) |
218 QVariant.typeToName(fld.type())) |
|
219 else: |
222 else: |
220 model.setData( |
223 model.setData( |
221 model.index(i, 1), "{0} ({1})".format( |
224 model.index(i, 1), |
222 QVariant.typeToName(fld.type()), fld.typeID())) |
225 "{0} ({1})".format(QVariant.typeToName(fld.type()), fld.typeID()), |
|
226 ) |
223 if fld.length() < 0: |
227 if fld.length() < 0: |
224 model.setData(model.index(i, 2), "?") |
228 model.setData(model.index(i, 2), "?") |
225 else: |
229 else: |
226 model.setData(model.index(i, 2), fld.length()) |
230 model.setData(model.index(i, 2), fld.length()) |
227 if fld.precision() < 0: |
231 if fld.precision() < 0: |
232 model.setData(model.index(i, 4), "?") |
236 model.setData(model.index(i, 4), "?") |
233 else: |
237 else: |
234 model.setData(model.index(i, 4), bool(fld.requiredStatus())) |
238 model.setData(model.index(i, 4), bool(fld.requiredStatus())) |
235 model.setData(model.index(i, 5), fld.isAutoValue()) |
239 model.setData(model.index(i, 5), fld.isAutoValue()) |
236 model.setData(model.index(i, 6), fld.defaultValue()) |
240 model.setData(model.index(i, 6), fld.defaultValue()) |
237 |
241 |
238 self.table.setModel(model) |
242 self.table.setModel(model) |
239 self.table.setEditTriggers( |
243 self.table.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers) |
240 QAbstractItemView.EditTrigger.NoEditTriggers) |
244 |
241 |
|
242 self.table.resizeColumnsToContents() |
245 self.table.resizeColumnsToContents() |
243 |
246 |
244 self.updateActions() |
247 self.updateActions() |
245 |
248 |
246 def updateActions(self): |
249 def updateActions(self): |
247 """ |
250 """ |
248 Public slot to update the actions. |
251 Public slot to update the actions. |
249 """ |
252 """ |
250 enableIns = isinstance(self.table.model(), QSqlTableModel) |
253 enableIns = isinstance(self.table.model(), QSqlTableModel) |
251 enableDel = enableIns & self.table.currentIndex().isValid() |
254 enableDel = enableIns & self.table.currentIndex().isValid() |
252 |
255 |
253 self.insertRowAction.setEnabled(enableIns) |
256 self.insertRowAction.setEnabled(enableIns) |
254 self.deleteRowAction.setEnabled(enableDel) |
257 self.deleteRowAction.setEnabled(enableDel) |
255 |
258 |
256 def __insertRow(self): |
259 def __insertRow(self): |
257 """ |
260 """ |
258 Private slot to insert a row into the database table. |
261 Private slot to insert a row into the database table. |
259 """ |
262 """ |
260 model = self.table.model() |
263 model = self.table.model() |
261 if not isinstance(model, QSqlTableModel): |
264 if not isinstance(model, QSqlTableModel): |
262 return |
265 return |
263 |
266 |
264 insertIndex = self.table.currentIndex() |
267 insertIndex = self.table.currentIndex() |
265 row = 0 if insertIndex.row() == -1 else insertIndex.row() |
268 row = 0 if insertIndex.row() == -1 else insertIndex.row() |
266 model.insertRow(row) |
269 model.insertRow(row) |
267 insertIndex = model.index(row, 0) |
270 insertIndex = model.index(row, 0) |
268 self.table.setCurrentIndex(insertIndex) |
271 self.table.setCurrentIndex(insertIndex) |
269 self.table.edit(insertIndex) |
272 self.table.edit(insertIndex) |
270 |
273 |
271 def __deleteRow(self): |
274 def __deleteRow(self): |
272 """ |
275 """ |
273 Private slot to delete a row from the database table. |
276 Private slot to delete a row from the database table. |
274 """ |
277 """ |
275 model = self.table.model() |
278 model = self.table.model() |
276 if not isinstance(model, QSqlTableModel): |
279 if not isinstance(model, QSqlTableModel): |
277 return |
280 return |
278 |
281 |
279 model.setEditStrategy(QSqlTableModel.EditStrategy.OnManualSubmit) |
282 model.setEditStrategy(QSqlTableModel.EditStrategy.OnManualSubmit) |
280 |
283 |
281 currentSelection = self.table.selectionModel().selectedIndexes() |
284 currentSelection = self.table.selectionModel().selectedIndexes() |
282 for selectedIndex in currentSelection: |
285 for selectedIndex in currentSelection: |
283 if selectedIndex.column() != 0: |
286 if selectedIndex.column() != 0: |
284 continue |
287 continue |
285 model.removeRow(selectedIndex.row()) |
288 model.removeRow(selectedIndex.row()) |
286 |
289 |
287 model.submitAll() |
290 model.submitAll() |
288 model.setEditStrategy(QSqlTableModel.EditStrategy.OnRowChange) |
291 model.setEditStrategy(QSqlTableModel.EditStrategy.OnRowChange) |
289 |
292 |
290 self.updateActions() |
293 self.updateActions() |
291 |
294 |
292 def executeQuery(self): |
295 def executeQuery(self): |
293 """ |
296 """ |
294 Public slot to execute the entered query. |
297 Public slot to execute the entered query. |
295 """ |
298 """ |
296 model = QSqlQueryModel(self.table) |
299 model = QSqlQueryModel(self.table) |
297 model.setQuery(QSqlQuery( |
300 model.setQuery( |
298 self.sqlEdit.toPlainText(), self.connections.currentDatabase())) |
301 QSqlQuery(self.sqlEdit.toPlainText(), self.connections.currentDatabase()) |
|
302 ) |
299 self.table.setModel(model) |
303 self.table.setModel(model) |
300 |
304 |
301 if model.lastError().type() != QSqlError.ErrorType.NoError: |
305 if model.lastError().type() != QSqlError.ErrorType.NoError: |
302 self.statusMessage.emit(model.lastError().text()) |
306 self.statusMessage.emit(model.lastError().text()) |
303 elif model.query().isSelect(): |
307 elif model.query().isSelect(): |
304 self.statusMessage.emit(self.tr("Query OK.")) |
308 self.statusMessage.emit(self.tr("Query OK.")) |
305 else: |
309 else: |
306 self.statusMessage.emit( |
310 self.statusMessage.emit( |
307 self.tr("Query OK, number of affected rows: {0}") |
311 self.tr("Query OK, number of affected rows: {0}").format( |
308 .format(model.query().numRowsAffected())) |
312 model.query().numRowsAffected() |
309 |
313 ) |
|
314 ) |
|
315 |
310 self.table.resizeColumnsToContents() |
316 self.table.resizeColumnsToContents() |
311 |
317 |
312 self.updateActions() |
318 self.updateActions() |