Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for mysql and sqlite #100

Merged
merged 18 commits into from
Sep 28, 2024
Prev Previous commit
Next Next commit
feat: impl parser for sqlite and musql
  • Loading branch information
Frank-III committed Sep 26, 2024
commit 8f287fc5f57d1a96d245bcb77bb1c21a32fbc2fc
29 changes: 12 additions & 17 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use ratatui::{
use serde::{Deserialize, Serialize};
use sqlparser::{
ast::Statement,
dialect::Dialect,
keywords::{DELETE, NAME},
};
use sqlx::{
Expand All @@ -39,7 +40,7 @@ use crate::{
Component,
},
config::Config,
database::{self, get_dialect, statement_type_string, DbError, DbPool, Rows},
database::{self, get_dialect, statement_type_string, DatabaseQueries, DbError, DbPool, Rows},
focus::Focus,
tui,
ui::center,
Expand All @@ -59,6 +60,7 @@ pub struct HistoryEntry {

pub struct AppState<'a, DB: Database> {
pub connection_opts: <DB::Connection as Connection>::Options,
pub dialect: Arc<dyn Dialect + Send + Sync>,
pub focus: Focus,
pub query_task: Option<DbTask<'a, DB>>,
pub history: Vec<HistoryEntry>,
Expand Down Expand Up @@ -92,7 +94,7 @@ pub struct App<'a, DB: sqlx::Database> {

impl<'a, DB> App<'a, DB>
where
DB: Database + database::ValueParser,
DB: Database + database::ValueParser + database::DatabaseQueries,
DB::QueryResult: database::HasRowsAffected,
for<'c> <DB as sqlx::Database>::Arguments<'c>: sqlx::IntoArguments<'c, DB>,
for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>,
Expand All @@ -118,6 +120,7 @@ where
pool: None,
state: AppState {
connection_opts,
dialect: get_dialect(DB::NAME),
focus,
query_task: None,
history: vec![],
Expand Down Expand Up @@ -357,37 +360,28 @@ where
Action::LoadMenu => {
log::info!("LoadMenu");
if let Some(pool) = &self.pool {
let results = database::query(
"select table_schema, table_name
from information_schema.tables
where table_schema != 'pg_catalog'
and table_schema != 'information_schema'
group by table_schema, table_name
order by table_schema, table_name asc"
.to_owned(),
pool,
)
.await;
let results = database::query(DB::preview_tables_query(), self.state.dialect.as_ref(), pool).await;
self.components.menu.set_table_list(Some(results));
}
},
Action::Query(query_lines) => {
let query_string = query_lines.clone().join(" \n");
if !query_string.is_empty() {
self.add_to_history(query_lines.clone());
let dialect = get_dialect(<DB as sqlx::Database>::NAME);
let first_query = database::get_first_query(query_string.clone(), dialect.as_ref());
let first_query = database::get_first_query(query_string.clone(), self.state.dialect.as_ref());
let should_use_tx = first_query
.map(|(_, statement_type)| (database::should_use_tx(statement_type.clone()), statement_type));
let action_tx = action_tx.clone();
if let Some(pool) = &self.pool {
let pool = pool.clone();
let dialect = self.state.dialect.clone();
match should_use_tx {
Ok((true, statement_type)) => {
self.components.data.set_loading();
let tx = pool.begin().await?;
self.state.query_task = Some(DbTask::TxStart(tokio::spawn(async move {
let (results, tx) = database::query_with_tx::<DB>(tx, query_string.clone()).await;
let (results, tx) =
database::query_with_tx::<DB>(tx, dialect.as_ref(), query_string.clone()).await;
match results {
Ok(Either::Left(rows_affected)) => {
log::info!("{:?} rows affected", rows_affected);
Expand All @@ -414,8 +408,9 @@ where
},
Ok((false, statement_type)) => {
self.components.data.set_loading();
let dialect = self.state.dialect.clone();
self.state.query_task = Some(DbTask::Query(tokio::spawn(async move {
let results = database::query(query_string.clone(), &pool).await;
let results = database::query(query_string.clone(), dialect.as_ref(), &pool).await;
match &results {
Ok(rows) => {
log::info!("{:?} rows, {:?} affected", rows.rows.len(), rows.rows_affected);
Expand Down
5 changes: 4 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@ pub struct Cli {
pub port: Option<u16>,

#[arg(long = "database", value_name = "DATABASE", help = "Name of database for connection (ex. postgres)")]
pub database: String,
pub database: Option<String>,

#[arg(long = "driver", value_name = "DRIVER", help = "Driver for database connection (ex. postgres)")]
pub driver: String,
}
10 changes: 2 additions & 8 deletions src/components/data.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{collections::HashMap, marker::PhantomData, sync::Arc, time::Duration};
use std::{collections::HashMap, sync::Arc, time::Duration};

use color_eyre::eyre::Result;
use crossterm::{
Expand Down Expand Up @@ -232,13 +232,7 @@ impl<'a> SettableDataTable<'a> for Data<'a> {
}
}

impl<'a, DB> Component<DB> for Data<'a>
where
DB: Database + crate::database::ValueParser,
DB::QueryResult: crate::database::HasRowsAffected,
for<'c> <DB as sqlx::Database>::Arguments<'c>: sqlx::IntoArguments<'c, DB>,
for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>,
{
impl<'a, DB: Database> Component<DB> for Data<'a> {
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
self.command_tx = Some(tx);
Ok(())
Expand Down
49 changes: 12 additions & 37 deletions src/components/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::{
action::{Action, MenuPreview},
app::{App, AppState, DbTask},
config::{Config, KeyBindings},
database::get_keywords,
database::{self, get_keywords, DatabaseQueries, HasRowsAffected, ValueParser},
focus::Focus,
tui::Event,
vim::{Mode, Transition, Vim},
Expand Down Expand Up @@ -67,13 +67,11 @@ impl<'a> Editor<'a> {
}
}

pub fn transition_vim_state<DB>(&mut self, input: Input, app_state: &AppState<'_, DB>) -> Result<()>
where
DB: Database + crate::database::ValueParser,
DB::QueryResult: crate::database::HasRowsAffected,
for<'c> <DB as sqlx::Database>::Arguments<'c>: sqlx::IntoArguments<'c, DB>,
for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>,
{
pub fn transition_vim_state<DB: Database + DatabaseQueries>(
&mut self,
input: Input,
app_state: &AppState<'_, DB>,
) -> Result<()> {
match input {
Input { key: Key::Enter, alt: true, .. } | Input { key: Key::Enter, ctrl: true, .. } => {
if app_state.query_task.is_none() {
Expand Down Expand Up @@ -115,13 +113,7 @@ impl<'a> Editor<'a> {
}
}

impl<'a, DB> Component<DB> for Editor<'a>
where
DB: Database + crate::database::ValueParser,
DB::QueryResult: crate::database::HasRowsAffected,
for<'c> <DB as sqlx::Database>::Arguments<'c>: sqlx::IntoArguments<'c, DB>,
for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>,
{
impl<'a, DB: Database + DatabaseQueries> Component<DB> for Editor<'a> {
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
self.command_tx = Some(tx);
Ok(())
Expand Down Expand Up @@ -181,28 +173,11 @@ where
return Ok(None);
}
let query = match preview_type {
MenuPreview::Rows => format!("select * from \"{}\".\"{}\" limit 100", schema, table),
MenuPreview::Columns => {
format!(
"select column_name, * from information_schema.columns where table_schema = '{}' and table_name = '{}'",
schema, table
)
},
MenuPreview::Constraints => {
format!(
"select constraint_name, * from information_schema.table_constraints where table_schema = '{}' and table_name = '{}'",
schema, table
)
},
MenuPreview::Indexes => {
format!(
"select indexname, indexdef, * from pg_indexes where schemaname = '{}' and tablename = '{}'",
schema, table
)
},
MenuPreview::Policies => {
format!("select * from pg_policies where schemaname = '{}' and tablename = '{}'", schema, table)
},
MenuPreview::Rows => DB::preview_rows_query(&schema, &table),
MenuPreview::Columns => DB::preview_columns_query(&schema, &table),
MenuPreview::Constraints => DB::preview_constraints_query(&schema, &table),
MenuPreview::Indexes => DB::preview_indexes_query(&schema, &table),
MenuPreview::Policies => DB::preview_policies_query(&schema, &table),
};
self.textarea = TextArea::from(vec![query.clone()]);
self.textarea.set_search_pattern(keyword_regex()).unwrap();
Expand Down
8 changes: 1 addition & 7 deletions src/components/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -196,13 +196,7 @@ impl<'a> SettableTableList<'a> for Menu {
}
}

impl<DB> Component<DB> for Menu
where
DB: Database + crate::database::ValueParser,
DB::QueryResult: crate::database::HasRowsAffected,
for<'c> <DB as sqlx::Database>::Arguments<'c>: sqlx::IntoArguments<'c, DB>,
for<'c> &'c mut DB::Connection: Executor<'c, Database = DB>,
{
impl<DB: Database> Component<DB> for Menu {
fn register_action_handler(&mut self, tx: UnboundedSender<Action>) -> Result<()> {
self.command_tx = Some(tx);
Ok(())
Expand Down
Loading