Commit 00dd8a38 authored by Javier Romero's avatar Javier Romero
parent 20868d8d
Pipeline #29526765 failed with stages
in 9 minutes and 4 seconds
## [Unreleased]
### Fixed
- Landscape lock screen:
https://gitlab.com/devlife-apps/connect2sql/issues/11
- Landscape lock screen: https://gitlab.com/devlife-apps/connect2sql/issues/11
- Crash: http://crashes.to/s/a5ca59edd6f
## [4.0.9] - 2018-09-04
### Added
- Spanish (es) translation
### Fixed
- Crash: http://crashes.to/s/3cfd4558aaa
## [4.0.8] - 2018-09-03
### Added
- Don't attempt to lookup all databases when database set
### Fixed
- Crash: http://crashes.to/s/de660f491cc
## [4.0.7] - 2018-08-27
### Added
- Beta link to menu
## [4.0.6] - 2018-08-27
### Added
- Cancel server graph when backgrounded.
### Fixed
- Upgrade support libraries for current target.
- Resolve http://crashes.to/s/107437021a3
- Resolve http://crashes.to/s/107437021a3
\ No newline at end of file
......@@ -7,19 +7,17 @@ import java.sql.Connection
import java.sql.ResultSet
import java.sql.SQLException
import java.sql.Statement
import java.sql.Types
/**
*/
class DefaultDriverAgent(private val mDriverHelper: DriverHelper) : DriverAgent {
class DefaultDriverAgent(private val driverHelper: DriverHelper) : DriverAgent {
override fun databases(connection: Connection): Observable<DriverAgent.Database> {
return Observable.create { subscriber ->
try {
val statement = connection.createStatement()
val resultSet = statement.executeQuery(mDriverHelper.databasesQuery)
val resultSet = statement.executeQuery(driverHelper.databasesQuery)
while (resultSet.next()) {
val name = resultSet.getString(mDriverHelper.databaseNameIndex)
val name = resultSet.getString(driverHelper.databaseNameIndex)
subscriber.onNext(DriverAgent.Database(name))
}
resultSet.close()
......@@ -40,10 +38,10 @@ class DefaultDriverAgent(private val mDriverHelper: DriverHelper) : DriverAgent
useDatabase(connection, databaseName)
val statement = connection.createStatement()
val resultSet = statement.executeQuery(mDriverHelper.getTablesQuery(databaseName))
val resultSet = statement.executeQuery(driverHelper.getTablesQuery(databaseName))
while (resultSet.next()) {
val tableName = resultSet.getString(mDriverHelper.tableNameIndex)
val tableType = resultSet.getString(mDriverHelper.tableTypeIndex)?.let {
val tableName = resultSet.getString(driverHelper.tableNameIndex)
val tableType = resultSet.getString(driverHelper.tableTypeIndex)?.let {
when (it) {
"SYSTEM VIEW",
"VIEW" ->
......@@ -80,10 +78,10 @@ class DefaultDriverAgent(private val mDriverHelper: DriverHelper) : DriverAgent
useDatabase(connection, databaseName)
val statement = connection.createStatement()
val resultSet = statement.executeQuery(mDriverHelper.getColumnsQuery(tableName))
val resultSet = statement.executeQuery(driverHelper.getColumnsQuery(tableName))
while (resultSet.next()) {
val columnName = resultSet.getString(mDriverHelper.columnNameIndex)
val columnName = resultSet.getString(driverHelper.columnNameIndex)
subscriber.onNext(DriverAgent.Column(columnName))
}
......@@ -101,7 +99,6 @@ class DefaultDriverAgent(private val mDriverHelper: DriverHelper) : DriverAgent
override fun execute(connection: Connection, databaseName: DriverAgent.Database?, sql: String): Observable<Statement> {
return Observable.create { subscriber ->
try {
if (databaseName != null) {
useDatabase(connection, databaseName)
}
......@@ -116,6 +113,49 @@ class DefaultDriverAgent(private val mDriverHelper: DriverHelper) : DriverAgent
}
}
override fun extract(resultSet: ResultSet, startIndex: Int, displayLimit: Int): Observable<DriverAgent.DisplayResults> {
return Observable.create { subscriber ->
try {
val metaData = resultSet.metaData
val columnCount = metaData.columnCount
resultSet.last()
val totalRows = resultSet.row
val columnNameAndTypes = (0 until columnCount).map { i -> Pair(
metaData.getColumnLabel(i + 1),
metaData.getColumnType(i + 1)
)}
val startRow = startIndex + 1
val lastDisplayedRow = when {
startRow + (displayLimit - 1) < totalRows -> startRow + (displayLimit - 1)
else -> totalRows
}
val data = (startRow..lastDisplayedRow).map { row ->
resultSet.absolute(row)
(0 until columnCount).map { columnIndex ->
when(columnNameAndTypes[columnIndex].second) {
Types.BLOB, Types.LONGVARBINARY -> "[blob]"
else -> resultSet.getString(columnIndex + 1) ?: "[null]"
}
}
}
subscriber.onNext(DriverAgent.DisplayResults(
columnNames = columnNameAndTypes.map { (name, _) -> name },
data = data,
totalCount = totalRows
))
} catch (e: SQLException) {
subscriber.onError(e)
}
}
}
override fun close(statement: Statement): Observable<Void> {
return Observable.create { subscriber ->
try {
......@@ -136,7 +176,7 @@ class DefaultDriverAgent(private val mDriverHelper: DriverHelper) : DriverAgent
*/
private fun useDatabase(connection: Connection, databaseName: DriverAgent.Database) {
val statement = connection.createStatement()
val useDatabaseSql = mDriverHelper.createUseDatabaseSql(databaseName)
val useDatabaseSql = driverHelper.createUseDatabaseSql(databaseName)
if (useDatabaseSql != null) {
if (statement.execute(useDatabaseSql)) {
statement.close()
......
......@@ -2,7 +2,9 @@ package app.devlife.connect2sql.sql.driver.agent
import rx.Observable
import java.sql.Connection
import java.sql.ResultSet
import java.sql.Statement
import java.util.LinkedList
/**
......@@ -17,6 +19,8 @@ interface DriverAgent {
fun execute(connection: Connection, databaseName: Database?, sql: String): Observable<Statement>
fun extract(resultSet: ResultSet, startIndex: Int, displayLimit: Int): Observable<DisplayResults>
fun close(statement: Statement): Observable<Void>
/**
......@@ -34,4 +38,6 @@ interface DriverAgent {
VIEW,
TABLE
}
data class DisplayResults(val columnNames: List<String>, val data: List<List<String>>, val totalCount: Int)
}
......@@ -9,10 +9,10 @@ object DriverHelperFactory {
private val driverHelpers = hashMapOf<DriverType, DriverHelper>()
init {
driverHelpers.put(DriverType.MSSQL, MsSqlDriverHelper())
driverHelpers.put(DriverType.MYSQL, MySqlDriverHelper())
driverHelpers.put(DriverType.POSTGRES, PostgresDriverHelper())
driverHelpers.put(DriverType.SYBASE, SybaseDriverHelper())
driverHelpers[DriverType.MSSQL] = MsSqlDriverHelper()
driverHelpers[DriverType.MYSQL] = MySqlDriverHelper()
driverHelpers[DriverType.POSTGRES] = PostgresDriverHelper()
driverHelpers[DriverType.SYBASE] = SybaseDriverHelper()
}
fun create(driverType: DriverType): DriverHelper? {
......
......@@ -8,7 +8,6 @@ import android.os.Bundle
import android.util.SparseArray
import android.view.Menu
import android.view.MenuItem
import com.gitlab.connect2sql.R
import app.devlife.connect2sql.ApplicationUtils
import app.devlife.connect2sql.activity.BaseActivity
import app.devlife.connect2sql.connection.ConnectionAgent
......@@ -22,6 +21,7 @@ import app.devlife.connect2sql.sql.driver.helper.DriverHelper
import app.devlife.connect2sql.sql.driver.helper.DriverHelperFactory
import app.devlife.connect2sql.ui.widget.dialog.ProgressDialog
import app.devlife.connect2sql.util.rx.ActivityAwareSubscriber
import com.gitlab.connect2sql.R
import rx.Subscriber
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
......@@ -32,18 +32,17 @@ import javax.inject.Inject
class ResultsActivity : BaseActivity() {
private val connectionInfo: ConnectionInfo by lazy {
val id = intent?.extras?.getLong(EXTRA_CONNECTION_INFO_ID).ensure({ t -> t != null && t > 0 })!!
connectionInfoRepo.getConnectionInfo(id)
}
private val sqlString: String by lazy { intent?.extras?.getString(EXTRA_SQL_STRING)!! }
private val databaseName: String? by lazy { intent?.extras?.getString(EXTRA_DATABASE) }
private val resultsSets = SparseArray<ResultSet>()
private val resultTableFragments = HashMap<ResultSet, ResultsTableFragment>()
private var progressDialog: ProgressDialog? = null
private val connectionInfo: ConnectionInfo by lazy {
val id = intent?.extras?.getLong(EXTRA_CONNECTION_INFO_ID).ensure { t -> t != null && t > 0 }!!
connectionInfoRepo.getConnectionInfo(id)
}
private lateinit var driverHelper: DriverHelper
private lateinit var driverAgent: DriverAgent
......@@ -234,10 +233,8 @@ class ResultsActivity : BaseActivity() {
if (resultTableFragments.containsKey(rs)) {
frag = resultTableFragments[rs]!!
} else {
frag = ResultsTableFragment.newInstance(rs, 0)
// store reference to table fragments
resultTableFragments.put(rs, frag)
frag = ResultsTableFragment.newInstance(driverAgent, rs, 0)
resultTableFragments[rs] = frag
}
val transaction = supportFragmentManager.beginTransaction()
......@@ -249,7 +246,7 @@ class ResultsActivity : BaseActivity() {
* Clears our list of results sets and closes them
*/
private fun clearResultSets() {
for (i in 0..resultsSets.size() - 1) {
for (i in 0 until resultsSets.size()) {
Thread(ResultSetClosingRunnable(resultsSets.get(resultsSets.keyAt(i)))).start()
}
......
package app.devlife.connect2sql.ui.results;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v7.app.AlertDialog;
import android.text.ClipboardManager;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.gitlab.connect2sql.R;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.List;
import app.devlife.connect2sql.fragment.BaseFragment;
import app.devlife.connect2sql.log.EzLogger;
import app.devlife.connect2sql.ui.widget.TableGrid;
import app.devlife.connect2sql.ui.widget.TableGrid.OnCellEventListener;
import app.devlife.connect2sql.ui.widget.Toast;
public class ResultsTableFragment extends BaseFragment implements
OnCellEventListener, android.view.View.OnClickListener {
private static final String TAG = ResultsTableFragment.class.getSimpleName();
private TableGrid mResultsTable;
private List<String[]> mData = new ArrayList<>();
private int mNormalFrozenColumnWidth = 0;
private TextView mPagingTextView;
private ResultSet mResultSet;
private ImageButton mPagingPrevButton;
private ImageButton mPagingNextButton;
public static ResultsTableFragment newInstance(ResultSet rs, int startIndex) {
ResultsTableFragment frag = new ResultsTableFragment();
//FIXME: We need this to rely on a host instead otherwise it WILL crash on orientation change.
frag.setResultSet(rs);
frag.setStartIndex(startIndex);
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
EzLogger.d("Creating fragment...");
if (container == null) {
return null;
}
EzLogger.d("Setting main content view...");
View v = inflater.inflate(R.layout.fragment_results_table, container, false);
mResultsTable = (TableGrid) v.findViewById(R.id.tg_results);
float scale = getActivity().getResources().getDisplayMetrics().density;
EzLogger.i("Scale: " + scale);
mResultsTable.setScale(scale);
mResultsTable.setOnCellEventListener(this);
LinearLayout paginationBar = (LinearLayout) v
.findViewById(R.id.pagination_bar);
mPagingTextView = (TextView) paginationBar.findViewById(R.id.text1);
mPagingPrevButton = ((ImageButton) paginationBar.findViewById(R.id.button1));
mPagingNextButton = ((ImageButton) paginationBar.findViewById(R.id.button2));
mPagingPrevButton.setOnClickListener(this);
mPagingNextButton.setOnClickListener(this);
setPaginationText(0, 0, 0);
return v;
}
@Override
public void onStart() {
super.onStart();
if (mResultSet != null) {
new ResultSetPullTask(mResultSet, mStartIndex, mDisplayLimit,
mResultSetPullListener).execute((Void) null);
} else {
getActivity().finish();
}
}
@Override
public void onCellClick(View cell, boolean isHeader, boolean isFrozenColumn) {
if (isFrozenColumn) {
if (mNormalFrozenColumnWidth == 0) {
EzLogger.d("Resizing frozen column...");
mNormalFrozenColumnWidth = mResultsTable
.resizeFrozenColumn(100);
} else {
EzLogger.d("Resizing (to original) frozen column...");
mResultsTable.resizeFrozenColumn(mNormalFrozenColumnWidth);
mNormalFrozenColumnWidth = 0;
}
}
}
@Override
@SuppressWarnings("deprecation")
public void onCellLongClick(View cell, boolean isHeader,
boolean isFrozenColumn) {
String text = ((TextView) cell).getText().toString();
ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(text);
Toast.makeText(getActivity(),
"'" + text + "' has been copied to clipboard.",
Toast.LENGTH_LONG).show();
}
public float increaseFontSize(int i) {
float fontSize = mResultsTable.getFontSize() + i;
mResultsTable.setFontSize(fontSize);
return fontSize;
}
public float decreaseFontSize(int i) {
float fontSize = mResultsTable.getFontSize() - i;
mResultsTable.setFontSize(fontSize);
return fontSize;
}
public void redrawTable() {
mResultsTable.clear();
mResultsTable.draw(mData);
}
private void setResultSet(ResultSet resultSet) {
mResultSet = resultSet;
}
protected void setPaginationText(int from, int to, int total) {
mPagingTextView.setText(getString(R.string.results_showing_records_to, from, to, total));
}
/***
* Pagination tracking variables
*/
private final int mDisplayLimit = 30;
private int mStartIndex = 1;
private int mTotalRows = 0;
private ResultSetPullTask.CallbackListener mResultSetPullListener = new ResultSetPullTask.CallbackListener() {
@Override
public void onProgressUpdate(int current, int total) {
Log.d(TAG, "onProgressUpdate: " + current + ", " + total);
}
@Override
public void onPreExecute() {
Log.d(TAG, "onPreExecute");
}
@Override
public void onError(Throwable throwable) {
Log.e(TAG, throwable.getMessage(), throwable);
final FragmentActivity activity = getActivity();
if (activity == null) {
EzLogger.w("Activity has gone away!");
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("Error");
builder.setMessage(throwable.getMessage());
builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
activity.finish();
}
});
builder.create().show();
}
@Override
public void onComplete(String[] columnsNames, List<String[]> data,
int totalRecords) {
Log.d(TAG, "columnsNames count: " + columnsNames.length);
Log.d(TAG, "data count: " + data.size());
Log.d(TAG, "totalRecords: " + totalRecords);
mData.clear();
mData.addAll(data);
mData.add(0, columnsNames);
redrawTable();
mTotalRows = totalRecords;
updatePaging(mStartIndex, data.size(), mTotalRows);
}
};
/**
* Sets the index of the record to retrieve on next execution of
* ProcessResultSet
*
* @param index
*/
private void setStartIndex(int index) {
if (index < 1) {
index = 1;
}
mStartIndex = index;
}
public int getPreviousStartIndex() {
int previousStartIndex = mStartIndex - mDisplayLimit;
if (previousStartIndex < 1) {
previousStartIndex = 1;
}
return previousStartIndex;
}
public int getNextStartIndex() {
return mStartIndex + mDisplayLimit;
}
private void updatePaging(int startIndex, int rows, int totalRows) {
int start = startIndex;
int end = startIndex + (rows - 1);
if (rows == 0) {
start--;
}
if (start <= 1) {
mPagingPrevButton.setEnabled(false);
} else {
mPagingPrevButton.setEnabled(true);
}
if (end >= totalRows) {
mPagingNextButton.setEnabled(false);
} else {
mPagingNextButton.setEnabled(true);
}
setPaginationText(start, end, totalRows);
}
@Override
public void onClick(View button) {
int id = button.getId();
switch (button.getId()) {
case R.id.button1:
// previous button
setStartIndex(getPreviousStartIndex());
mResultsTable.clear();
mData.clear();
new ResultSetPullTask(mResultSet, mStartIndex, mDisplayLimit,
mResultSetPullListener).execute((Void) null);
break;
case R.id.button2:
// next button
setStartIndex(getNextStartIndex());
mResultsTable.clear();
mData.clear();
new ResultSetPullTask(mResultSet, mStartIndex, mDisplayLimit,
mResultSetPullListener).execute((Void) null);
break;
default:
Log.wtf(TAG, "What does this button do? " + id);
break;
}
}
}
\ No newline at end of file
package app.devlife.connect2sql.ui.results
import android.content.Context
import android.os.Bundle
import android.support.v7.app.AlertDialog
import android.text.ClipboardManager
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.LinearLayout
import android.widget.TextView
import app.devlife.connect2sql.fragment.BaseFragment
import app.devlife.connect2sql.log.EzLogger
import app.devlife.connect2sql.sql.driver.agent.DriverAgent
import app.devlife.connect2sql.ui.widget.TableGrid
import app.devlife.connect2sql.ui.widget.TableGrid.OnCellEventListener
import app.devlife.connect2sql.ui.widget.Toast
import app.devlife.connect2sql.util.rx.ActivityAwareSubscriber
import com.gitlab.connect2sql.R
import rx.Subscriber
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import java.sql.ResultSet
class ResultsTableFragment : BaseFragment(), OnCellEventListener, android.view.View.OnClickListener {
private var resultsTable: TableGrid? = null
private val data = mutableListOf<List<String>>()
private var normalFrozenColumnWidth = 0
private var pagingTextView: TextView? = null
private var resultSet: ResultSet? = null
private var pagingPrevButton: ImageButton? = null
private var pagingNextButton: ImageButton? = null
private var startIndex = 0
private var totalRows = 0
private val previousStartIndex: Int
get() {
var previousStartIndex = startIndex - DISPLAY_LIMIT
if (previousStartIndex < 0) {
previousStartIndex = 0
}
return previousStartIndex
}
private val nextStartIndex: Int
get() = startIndex + DISPLAY_LIMIT
private lateinit var driverAgent: DriverAgent
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
EzLogger.d("Creating fragment...")
if (container == null) {
return null
}
EzLogger.d("Setting main content view...")
val v = inflater.inflate(R.layout.fragment_results_table, container, false)
resultsTable = v.findViewById<View>(R.id.tg_results) as TableGrid
val scale = activity!!.resources.displayMetrics.density
EzLogger.i("Scale: $scale")
resultsTable!!.setScale(scale)
resultsTable!!.setOnCellEventListener(this)
val paginationBar = v.findViewById<View>(R.id.pagination_bar) as LinearLayout
pagingTextView = paginationBar.findViewById<View>(R.id.text1) as TextView
pagingPrevButton = paginationBar.findViewById<View>(R.id.button1) as ImageButton
pagingNextButton = paginationBar.findViewById<View>(R.id.button2) as ImageButton
pagingPrevButton!!.setOnClickListener(this)