1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <core_resource.hxx>
21 #include <directsql.hxx>
22 #include <sqledit.hxx>
23 #include <strings.hxx>
24 #include <strings.hrc>
25 #include <comphelper/types.hxx>
26 #include <osl/mutex.hxx>
27 #include <rtl/ustrbuf.hxx>
28 #include <rtl/ustring.hxx>
29 #include <comphelper/diagnose_ex.hxx>
30 #include <vcl/svapp.hxx>
31 #include <vcl/weld.hxx>
32 #include <com/sun/star/sdbc/SQLException.hpp>
33 #include <com/sun/star/sdbc/XRow.hpp>
34 #include <com/sun/star/beans/XPropertySet.hpp>
35 #include <com/sun/star/sdbc/DataType.hpp>
36 #include <com/sun/star/sdbc/XMultipleResults.hpp>
37 #include <com/sun/star/sdbc/XResultSetMetaData.hpp>
38 #include <com/sun/star/sdbc/XResultSetMetaDataSupplier.hpp>
40 // tdf#140298 - remember user settings within the current session
41 // memp is filled in dtor and restored after initialization
45 std::vector
<OUString
> SQLHistory
;
55 using namespace ::com::sun::star::uno
;
56 using namespace ::com::sun::star::sdbc
;
57 using namespace ::com::sun::star::lang
;
59 constexpr sal_Int32 g_nHistoryLimit
= 20;
62 DirectSQLDialog::DirectSQLDialog(weld::Window
* _pParent
, const Reference
< XConnection
>& _rxConn
)
63 : GenericDialogController(_pParent
, u
"dbaccess/ui/directsqldialog.ui"_ustr
, u
"DirectSQLDialog"_ustr
)
64 , m_xExecute(m_xBuilder
->weld_button(u
"execute"_ustr
))
65 , m_xSQLHistory(m_xBuilder
->weld_combo_box(u
"sqlhistory"_ustr
))
66 , m_xStatus(m_xBuilder
->weld_text_view(u
"status"_ustr
))
67 , m_xDirectSQL(m_xBuilder
->weld_check_button(u
"directsql"_ustr
))
68 , m_xShowOutput(m_xBuilder
->weld_check_button(u
"showoutput"_ustr
))
69 , m_xOutput(m_xBuilder
->weld_text_view(u
"output"_ustr
))
70 , m_xClose(m_xBuilder
->weld_button(u
"close"_ustr
))
71 , m_xSQL(new SQLEditView(m_xBuilder
->weld_scrolled_window(u
"scrolledwindow"_ustr
, true)))
72 , m_xSQLEd(new weld::CustomWeld(*m_xBuilder
, u
"sql"_ustr
, *m_xSQL
))
74 , m_xConnection(_rxConn
)
75 , m_pClosingEvent(nullptr)
77 int nWidth
= m_xStatus
->get_approximate_digit_width() * 60;
78 int nHeight
= m_xStatus
->get_height_rows(7);
80 m_xSQLEd
->set_size_request(nWidth
, nHeight
);
81 m_xStatus
->set_size_request(-1, nHeight
);
82 m_xOutput
->set_size_request(-1, nHeight
);
86 m_xExecute
->connect_clicked(LINK(this, DirectSQLDialog
, OnExecute
));
87 m_xClose
->connect_clicked(LINK(this, DirectSQLDialog
, OnCloseClick
));
88 m_xSQLHistory
->connect_changed(LINK(this, DirectSQLDialog
, OnListEntrySelected
));
90 m_xDirectSQL
->set_active(true);
91 m_xShowOutput
->set_active(true);
93 for (size_t i
= 0; i
< memp
.SQLHistory
.size(); i
++)
95 implAddToStatementHistory(memp
.SQLHistory
[i
], true);
96 m_xDirectSQL
->set_active(memp
.DirectSQL
);
97 m_xShowOutput
->set_active(memp
.ShowOutput
);
100 // add a dispose listener to the connection
101 Reference
< XComponent
> xConnComp(m_xConnection
, UNO_QUERY
);
102 OSL_ENSURE(xConnComp
.is(), "DirectSQLDialog::DirectSQLDialog: invalid connection!");
104 startComponentListening(xConnComp
);
106 m_xSQL
->SetModifyHdl(LINK(this, DirectSQLDialog
, OnStatementModified
));
107 OnStatementModified(nullptr);
110 DirectSQLDialog::~DirectSQLDialog()
112 memp
.DirectSQL
= m_xDirectSQL
->get_active();
113 memp
.ShowOutput
= m_xShowOutput
->get_active();
115 ::osl::MutexGuard
aGuard(m_aMutex
);
117 Application::RemoveUserEvent(m_pClosingEvent
);
118 stopAllComponentListening();
121 void DirectSQLDialog::_disposing( const EventObject
& _rSource
)
123 SolarMutexGuard aSolarGuard
;
124 ::osl::MutexGuard
aGuard(m_aMutex
);
126 assert(!m_pClosingEvent
);
128 OSL_ENSURE(Reference
< XConnection
>(_rSource
.Source
, UNO_QUERY
).get() == m_xConnection
.get(),
129 "DirectSQLDialog::_disposing: where does this come from?");
132 OUString
sMessage(DBA_RES(STR_DIRECTSQL_CONNECTIONLOST
));
133 std::unique_ptr
<weld::MessageDialog
> xError(Application::CreateMessageDialog(m_xDialog
.get(),
134 VclMessageType::Warning
, VclButtonsType::Ok
,
139 m_pClosingEvent
= Application::PostUserEvent(LINK(this, DirectSQLDialog
, OnClose
));
142 sal_Int32
DirectSQLDialog::getHistorySize() const
146 const char* pError
= impl_CheckInvariants();
148 SAL_WARN("dbaccess.ui", "DirectSQLDialog::getHistorySize: " << pError
);
151 return m_aStatementHistory
.size();
154 void DirectSQLDialog::implEnsureHistoryLimit()
158 const char* pError
= impl_CheckInvariants();
160 SAL_WARN("dbaccess.ui", "DirectSQLDialog::implEnsureHistoryLimit: " << pError
);
164 if (getHistorySize() <= g_nHistoryLimit
)
168 sal_Int32 nRemoveEntries
= getHistorySize() - g_nHistoryLimit
;
169 while (nRemoveEntries
--)
171 m_aStatementHistory
.pop_front();
172 m_aNormalizedHistory
.pop_front();
173 m_xSQLHistory
->remove(0);
177 void DirectSQLDialog::implAddToStatementHistory(const OUString
& _rStatement
, const bool bFromMemory
)
181 const char* pError
= impl_CheckInvariants();
183 SAL_WARN("dbaccess.ui", "DirectSQLDialog::implAddToStatementHistor: " << pError
);
187 // add the statement to the history
188 m_aStatementHistory
.push_back(_rStatement
);
190 memp
.SQLHistory
.push_back(_rStatement
);
192 // normalize the statement, and remember the normalized form, too
193 OUString sNormalized
= _rStatement
.replaceAll("\n", " ");
194 m_aNormalizedHistory
.push_back(sNormalized
);
196 // add the normalized version to the list box
197 m_xSQLHistory
->append_text(sNormalized
);
199 // ensure that we don't exceed the history limit
200 implEnsureHistoryLimit();
204 const char* DirectSQLDialog::impl_CheckInvariants() const
206 if (m_aStatementHistory
.size() != m_aNormalizedHistory
.size())
207 return "statement history is inconsistent!";
210 return "invalid listbox!";
212 if (m_aStatementHistory
.size() != static_cast<size_t>(m_xSQLHistory
->get_count()))
213 return "invalid listbox entry count!";
215 if (!m_xConnection
.is())
216 return "have no connection!";
222 void DirectSQLDialog::implExecuteStatement(const OUString
& _rStatement
)
226 const char* pError
= impl_CheckInvariants();
228 SAL_WARN("dbaccess.ui", "DirectSQLDialog::implExecuteStatement: " << pError
);
232 ::osl::MutexGuard
aGuard(m_aMutex
);
236 // clear the output box
237 m_xOutput
->set_text(OUString());
240 // create a statement
241 Reference
< XStatement
> xStatement
= m_xConnection
->createStatement();
243 if (m_xDirectSQL
->get_active())
245 Reference
< css::beans::XPropertySet
> xStatementProps(xStatement
, UNO_QUERY_THROW
);
248 xStatementProps
->setPropertyValue(PROPERTY_ESCAPE_PROCESSING
, Any(false));
250 catch( const Exception
& )
252 DBG_UNHANDLED_EXCEPTION("dbaccess");
256 Reference
<XDatabaseMetaData
> xMeta
= m_xConnection
->getMetaData();
257 css::uno::Reference
< css::sdbc::XMultipleResults
> xMR ( xStatement
, UNO_QUERY
);
259 if (xMeta
.is() && xMeta
->supportsMultipleResultSets() && xMR
.is())
261 bool hasRS
= xStatement
->execute(_rStatement
);
264 css::uno::Reference
< css::sdbc::XResultSet
> xRS (xMR
->getResultSet());
265 if (m_xShowOutput
->get_active())
270 Concat2View(OUString::number(xMR
->getUpdateCount()) + " rows updated\n"));
273 hasRS
= xMR
->getMoreResults();
274 if (!hasRS
&& xMR
->getUpdateCount() == -1)
278 css::uno::Reference
< css::sdbc::XResultSet
> xRS (xMR
->getResultSet());
279 if (m_xShowOutput
->get_active())
286 const OUString upperStatement
= _rStatement
.toAsciiUpperCase();
287 if (upperStatement
.startsWith("UPDATE"))
289 sal_Int32 resultCount
= xStatement
->executeUpdate(_rStatement
);
290 addOutputText(Concat2View(OUString::number(resultCount
) + " rows updated\n"));
292 else if (upperStatement
.startsWith("INSERT"))
294 sal_Int32 resultCount
= xStatement
->executeUpdate(_rStatement
);
295 addOutputText(Concat2View(OUString::number(resultCount
) + " rows inserted\n"));
297 else if (upperStatement
.startsWith("DELETE"))
299 sal_Int32 resultCount
= xStatement
->executeUpdate(_rStatement
);
300 addOutputText(Concat2View(OUString::number(resultCount
) + " rows deleted\n"));
302 else if (upperStatement
.startsWith("CREATE"))
304 xStatement
->executeUpdate(_rStatement
);
305 addOutputText(u
"Command executed\n");
307 else if (upperStatement
.startsWith("SELECT") || m_xShowOutput
->get_active())
309 css::uno::Reference
< css::sdbc::XResultSet
> xRS
= xStatement
->executeQuery(_rStatement
);
310 if (m_xShowOutput
->get_active())
315 sal_Int32 resultCount
= xStatement
->executeUpdate(_rStatement
);
316 addOutputText(Concat2View(OUString::number(resultCount
) + " rows updated\n"));
320 sStatus
= DBA_RES(STR_COMMAND_EXECUTED_SUCCESSFULLY
);
322 // dispose the statement
323 ::comphelper::disposeComponent(xStatement
);
325 catch(const SQLException
& e
)
329 catch( const Exception
& )
331 DBG_UNHANDLED_EXCEPTION("dbaccess");
334 // add the status text
335 addStatusText(sStatus
);
338 void DirectSQLDialog::display(const css::uno::Reference
< css::sdbc::XResultSet
>& xRS
)
341 const Reference
<XResultSetMetaData
> xResultSetMetaData
= Reference
<XResultSetMetaDataSupplier
>(xRS
,UNO_QUERY_THROW
)->getMetaData();
342 const sal_Int32 nColumnsCount
= xResultSetMetaData
->getColumnCount();
343 sal_Int32 nRowCount
= 0;
344 // get a handle for the rows
345 css::uno::Reference
< css::sdbc::XRow
> xRow( xRS
, css::uno::UNO_QUERY
);
346 // work through each of the rows
349 // initialise the output line for each row
351 // work along the columns until that are none left
354 for (sal_Int32 i
= 1; i
<= nColumnsCount
; ++i
)
356 switch (xResultSetMetaData
->getColumnType(i
))
358 // tdf#153317, at least "Bit" type in Mysql/MariaDB gives: "\000" or "\001"
359 // so retrieve Sequence from getBytes, test if it has a length of 1 (so we avoid BLOB/CLOB or other complex types)
360 // and test if the value of first byte is one of those.
361 // In this case, there's a good chance it's a "Bit" field
362 case css::sdbc::DataType::BIT
:
364 auto seq
= xRow
->getBytes(i
);
365 if ((seq
.getLength() == 1) && (seq
[0] >= 0) && (seq
[0] <= 1))
367 out
.append(OUString::number(static_cast<int>(seq
[0])) + ",");
371 out
.append(xRow
->getString(i
) + ",");
375 // for the rest, be dumb, treat everything as a string
377 out
.append(xRow
->getString(i
) + ",");
382 // trap for when we fall off the end of the row
383 catch (const SQLException
&)
389 addOutputText(DBA_RES_PLURAL(STR_COMMAND_NROWS
, nRowCount
).replaceAll("%1", OUString::number(nRowCount
)));
392 void DirectSQLDialog::addStatusText(std::u16string_view _rMessage
)
394 OUString sAppendMessage
= OUString::number(m_nStatusCount
++) + ": " + _rMessage
+ "\n\n";
396 OUString sCompleteMessage
= m_xStatus
->get_text() + sAppendMessage
;
397 m_xStatus
->set_text(sCompleteMessage
);
399 m_xStatus
->select_region(sCompleteMessage
.getLength(), sCompleteMessage
.getLength());
402 void DirectSQLDialog::addOutputText(std::u16string_view _rMessage
)
404 OUString sAppendMessage
= OUString::Concat(_rMessage
) + "\n";
406 OUString sCompleteMessage
= m_xOutput
->get_text() + sAppendMessage
;
407 m_xOutput
->set_text(sCompleteMessage
);
410 void DirectSQLDialog::executeCurrent()
414 const char* pError
= impl_CheckInvariants();
416 SAL_WARN("dbaccess.ui", "DirectSQLDialog::executeCurrent: " << pError
);
420 OUString sStatement
= m_xSQL
->GetText();
423 implExecuteStatement(sStatement
);
425 // add the statement to the history
426 implAddToStatementHistory(sStatement
);
431 void DirectSQLDialog::switchToHistory(sal_Int32 _nHistoryPos
)
435 const char* pError
= impl_CheckInvariants();
437 SAL_WARN("dbaccess.ui", "DirectSQLDialog::switchToHistory: " << pError
);
441 if ((_nHistoryPos
>= 0) && (_nHistoryPos
< getHistorySize()))
443 // set the text in the statement editor
444 OUString sStatement
= m_aStatementHistory
[_nHistoryPos
];
445 m_xSQL
->SetTextAndUpdate(sStatement
);
446 OnStatementModified(nullptr);
451 OSL_FAIL("DirectSQLDialog::switchToHistory: invalid position!");
454 IMPL_LINK_NOARG( DirectSQLDialog
, OnStatementModified
, LinkParamNone
*, void )
456 m_xExecute
->set_sensitive(!m_xSQL
->GetText().isEmpty());
459 IMPL_LINK_NOARG( DirectSQLDialog
, OnCloseClick
, weld::Button
&, void )
461 m_xDialog
->response(RET_OK
);
464 IMPL_LINK_NOARG( DirectSQLDialog
, OnClose
, void*, void )
466 assert(m_pClosingEvent
);
467 Application::RemoveUserEvent(m_pClosingEvent
);
468 m_pClosingEvent
= nullptr;
470 m_xDialog
->response(RET_OK
);
473 IMPL_LINK_NOARG( DirectSQLDialog
, OnExecute
, weld::Button
&, void )
478 IMPL_LINK_NOARG( DirectSQLDialog
, OnListEntrySelected
, weld::ComboBox
&, void )
480 const sal_Int32 nSelected
= m_xSQLHistory
->get_active();
482 switchToHistory(nSelected
);
487 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */