Merge branch 'master' into ss/test_suite

# Conflicts:
#	.github/workflows/rust.yml
#	Cargo.toml
This commit is contained in:
Sam Samai 2021-07-22 21:04:36 +10:00
commit 1d55df9c7b
17 changed files with 461 additions and 38 deletions

View File

@ -28,6 +28,19 @@ jobs:
ports: ports:
- "3306:3306" - "3306:3306"
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
postgres:
image: postgres:11
env:
POSTGRES_HOST: 127.0.0.1
POSTGRES_USER: root
POSTGRES_PASSWORD: root
ports:
- "5432:5432"
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -64,12 +64,13 @@ maplit = { version = "^1" }
rust_decimal_macros = { version = "^1" } rust_decimal_macros = { version = "^1" }
sea-orm = { path = ".", features = [ sea-orm = { path = ".", features = [
"sqlx-postgres",
"sqlx-mysql",
"sqlx-sqlite", "sqlx-sqlite",
"sqlx-json", "sqlx-json",
"sqlx-chrono", "sqlx-chrono",
"sqlx-decimal", "sqlx-decimal",
"runtime-async-std-native-tls", "runtime-async-std-native-tls",
"sqlx-mysql",
] } ] }
[features] [features]

View File

@ -4,6 +4,8 @@ use sea_query::{MysqlQueryBuilder, PostgresQueryBuilder, QueryBuilder, SqliteQue
pub enum DatabaseConnection { pub enum DatabaseConnection {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
SqlxMySqlPoolConnection(crate::SqlxMySqlPoolConnection), SqlxMySqlPoolConnection(crate::SqlxMySqlPoolConnection),
#[cfg(feature = "sqlx-postgres")]
SqlxPostgresPoolConnection(crate::SqlxPostgresPoolConnection),
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
SqlxSqlitePoolConnection(crate::SqlxSqlitePoolConnection), SqlxSqlitePoolConnection(crate::SqlxSqlitePoolConnection),
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
@ -36,6 +38,8 @@ impl std::fmt::Debug for DatabaseConnection {
match self { match self {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
Self::SqlxMySqlPoolConnection(_) => "SqlxMySqlPoolConnection", Self::SqlxMySqlPoolConnection(_) => "SqlxMySqlPoolConnection",
#[cfg(feature = "sqlx-postgres")]
Self::SqlxPostgresPoolConnection(_) => "SqlxMySqlPoolConnection",
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
Self::SqlxSqlitePoolConnection(_) => "SqlxSqlitePoolConnection", Self::SqlxSqlitePoolConnection(_) => "SqlxSqlitePoolConnection",
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
@ -51,6 +55,8 @@ impl DatabaseConnection {
match self { match self {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
DatabaseConnection::SqlxMySqlPoolConnection(_) => DbBackend::MySql, DatabaseConnection::SqlxMySqlPoolConnection(_) => DbBackend::MySql,
#[cfg(feature = "sqlx-postgres")]
DatabaseConnection::SqlxPostgresPoolConnection(_) => DbBackend::Postgres,
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
DatabaseConnection::SqlxSqlitePoolConnection(_) => DbBackend::Sqlite, DatabaseConnection::SqlxSqlitePoolConnection(_) => DbBackend::Sqlite,
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
@ -63,6 +69,8 @@ impl DatabaseConnection {
match self { match self {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.execute(stmt).await, DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.execute(stmt).await,
#[cfg(feature = "sqlx-postgres")]
DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.execute(stmt).await,
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.execute(stmt).await, DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.execute(stmt).await,
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
@ -75,6 +83,8 @@ impl DatabaseConnection {
match self { match self {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_one(stmt).await, DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_one(stmt).await,
#[cfg(feature = "sqlx-postgres")]
DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.query_one(stmt).await,
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_one(stmt).await, DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_one(stmt).await,
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
@ -87,6 +97,8 @@ impl DatabaseConnection {
match self { match self {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_all(stmt).await, DatabaseConnection::SqlxMySqlPoolConnection(conn) => conn.query_all(stmt).await,
#[cfg(feature = "sqlx-postgres")]
DatabaseConnection::SqlxPostgresPoolConnection(conn) => conn.query_all(stmt).await,
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_all(stmt).await, DatabaseConnection::SqlxSqlitePoolConnection(conn) => conn.query_all(stmt).await,
#[cfg(feature = "mock")] #[cfg(feature = "mock")]

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
error::*, DbBackend, DatabaseConnection, EntityTrait, ExecResult, ExecResultHolder, Iden, error::*, DatabaseConnection, DbBackend, EntityTrait, ExecResult, ExecResultHolder, Iden,
Iterable, MockDatabaseConnection, MockDatabaseTrait, ModelTrait, QueryResult, QueryResultRow, Iterable, MockDatabaseConnection, MockDatabaseTrait, ModelTrait, QueryResult, QueryResultRow,
Statement, Transaction, Statement, Transaction,
}; };

View File

@ -21,6 +21,10 @@ impl Database {
if crate::SqlxMySqlConnector::accepts(string) { if crate::SqlxMySqlConnector::accepts(string) {
return crate::SqlxMySqlConnector::connect(string).await; return crate::SqlxMySqlConnector::connect(string).await;
} }
#[cfg(feature = "sqlx-postgres")]
if crate::SqlxPostgresConnector::accepts(string) {
return crate::SqlxPostgresConnector::connect(string).await;
}
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
if crate::SqlxSqliteConnector::accepts(string) { if crate::SqlxSqliteConnector::accepts(string) {
return crate::SqlxSqliteConnector::connect(string).await; return crate::SqlxSqliteConnector::connect(string).await;

View File

@ -1,7 +1,6 @@
use crate::DbBackend; use crate::DbBackend;
use sea_query::{ use sea_query::{inject_parameters, MysqlQueryBuilder, PostgresQueryBuilder, SqliteQueryBuilder};
inject_parameters, MysqlQueryBuilder, PostgresQueryBuilder, SqliteQueryBuilder, Value, Values pub use sea_query::{Value, Values};
};
use std::fmt; use std::fmt;
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]

View File

@ -1,6 +1,6 @@
use crate::{ use crate::{
debug_print, error::*, DbBackend, DatabaseConnection, ExecResult, MockDatabase, debug_print, error::*, DatabaseConnection, DbBackend, ExecResult, MockDatabase, QueryResult,
QueryResult, Statement, Transaction, Statement, Transaction,
}; };
use std::sync::{ use std::sync::{
atomic::{AtomicUsize, Ordering}, atomic::{AtomicUsize, Ordering},
@ -30,6 +30,10 @@ impl MockDatabaseConnector {
if crate::SqlxMySqlConnector::accepts(string) { if crate::SqlxMySqlConnector::accepts(string) {
return true; return true;
} }
#[cfg(feature = "sqlx-postgres")]
if crate::SqlxPostgresConnector::accepts(string) {
return true;
}
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
if crate::SqlxSqliteConnector::accepts(string) { if crate::SqlxSqliteConnector::accepts(string) {
return true; return true;
@ -50,6 +54,10 @@ impl MockDatabaseConnector {
if crate::SqlxMySqlConnector::accepts(string) { if crate::SqlxMySqlConnector::accepts(string) {
return connect_mock_db!(DbBackend::MySql); return connect_mock_db!(DbBackend::MySql);
} }
#[cfg(feature = "sqlx-postgres")]
if crate::SqlxPostgresConnector::accepts(string) {
return connect_mock_db!(DbBackend::Postgres);
}
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
if crate::SqlxSqliteConnector::accepts(string) { if crate::SqlxSqliteConnector::accepts(string) {
return connect_mock_db!(DbBackend::Sqlite); return connect_mock_db!(DbBackend::Sqlite);

View File

@ -4,6 +4,8 @@ mod mock;
mod sqlx_common; mod sqlx_common;
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
mod sqlx_mysql; mod sqlx_mysql;
#[cfg(feature = "sqlx-postgres")]
mod sqlx_postgres;
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
mod sqlx_sqlite; mod sqlx_sqlite;
@ -13,5 +15,7 @@ pub use mock::*;
pub use sqlx_common::*; pub use sqlx_common::*;
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
pub use sqlx_mysql::*; pub use sqlx_mysql::*;
#[cfg(feature = "sqlx-postgres")]
pub use sqlx_postgres::*;
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
pub use sqlx_sqlite::*; pub use sqlx_sqlite::*;

129
src/driver/sqlx_postgres.rs Normal file
View File

@ -0,0 +1,129 @@
use sqlx::{
postgres::{PgArguments, PgQueryResult, PgRow},
PgPool, Postgres,
};
sea_query::sea_query_driver_postgres!();
use sea_query_driver_postgres::bind_query;
use crate::{debug_print, error::*, executor::*, DatabaseConnection, Statement};
use super::sqlx_common::*;
pub struct SqlxPostgresConnector;
pub struct SqlxPostgresPoolConnection {
pool: PgPool,
}
impl SqlxPostgresConnector {
pub fn accepts(string: &str) -> bool {
string.starts_with("postgres://")
}
pub async fn connect(string: &str) -> Result<DatabaseConnection, DbErr> {
if let Ok(pool) = PgPool::connect(string).await {
Ok(DatabaseConnection::SqlxPostgresPoolConnection(
SqlxPostgresPoolConnection { pool },
))
} else {
Err(DbErr::Conn("Failed to connect.".to_owned()))
}
}
}
impl SqlxPostgresConnector {
pub fn from_sqlx_postgres_pool(pool: PgPool) -> DatabaseConnection {
DatabaseConnection::SqlxPostgresPoolConnection(SqlxPostgresPoolConnection { pool })
}
}
impl SqlxPostgresPoolConnection {
pub async fn execute(&self, stmt: Statement) -> Result<ExecResult, DbErr> {
debug_print!("{}", stmt);
let query = sqlx_query(&stmt);
if let Ok(conn) = &mut self.pool.acquire().await {
match query.execute(conn).await {
Ok(res) => Ok(res.into()),
Err(err) => Err(sqlx_error_to_exec_err(err)),
}
} else {
Err(DbErr::Exec(
"Failed to acquire connection from pool.".to_owned(),
))
}
}
pub async fn query_one(&self, stmt: Statement) -> Result<Option<QueryResult>, DbErr> {
debug_print!("{}", stmt);
let query = sqlx_query(&stmt);
if let Ok(conn) = &mut self.pool.acquire().await {
match query.fetch_one(conn).await {
Ok(row) => Ok(Some(row.into())),
Err(err) => match err {
sqlx::Error::RowNotFound => Ok(None),
_ => Err(DbErr::Query(err.to_string())),
},
}
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
}
}
pub async fn query_all(&self, stmt: Statement) -> Result<Vec<QueryResult>, DbErr> {
debug_print!("{}", stmt);
let query = sqlx_query(&stmt);
if let Ok(conn) = &mut self.pool.acquire().await {
match query.fetch_all(conn).await {
Ok(rows) => Ok(rows.into_iter().map(|r| r.into()).collect()),
Err(err) => Err(sqlx_error_to_query_err(err)),
}
} else {
Err(DbErr::Query(
"Failed to acquire connection from pool.".to_owned(),
))
}
}
}
impl From<PgRow> for QueryResult {
fn from(row: PgRow) -> QueryResult {
QueryResult {
row: QueryResultRow::SqlxPostgres(row),
}
}
}
impl From<PgQueryResult> for ExecResult {
fn from(result: PgQueryResult) -> ExecResult {
ExecResult {
result: ExecResultHolder::SqlxPostgres {
last_insert_id: 0,
rows_affected: result.rows_affected(),
},
}
}
}
pub(crate) fn query_result_into_exec_result(res: QueryResult) -> Result<ExecResult, DbErr> {
let last_insert_id: i32 = res.try_get("", "last_insert_id")?;
Ok(ExecResult {
result: ExecResultHolder::SqlxPostgres {
last_insert_id: last_insert_id as u64,
rows_affected: 0,
},
})
}
fn sqlx_query(stmt: &Statement) -> sqlx::query::Query<'_, Postgres, PgArguments> {
let mut query = sqlx::query(&stmt.sql);
if let Some(values) = &stmt.values {
query = bind_query(query, values);
}
query
}

View File

@ -7,6 +7,11 @@ pub struct ExecResult {
pub(crate) enum ExecResultHolder { pub(crate) enum ExecResultHolder {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
SqlxMySql(sqlx::mysql::MySqlQueryResult), SqlxMySql(sqlx::mysql::MySqlQueryResult),
#[cfg(feature = "sqlx-postgres")]
SqlxPostgres {
last_insert_id: u64,
rows_affected: u64,
},
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
SqlxSqlite(sqlx::sqlite::SqliteQueryResult), SqlxSqlite(sqlx::sqlite::SqliteQueryResult),
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
@ -20,6 +25,8 @@ impl ExecResult {
match &self.result { match &self.result {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
ExecResultHolder::SqlxMySql(result) => result.last_insert_id(), ExecResultHolder::SqlxMySql(result) => result.last_insert_id(),
#[cfg(feature = "sqlx-postgres")]
ExecResultHolder::SqlxPostgres { last_insert_id, .. } => last_insert_id.to_owned(),
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
ExecResultHolder::SqlxSqlite(result) => { ExecResultHolder::SqlxSqlite(result) => {
let last_insert_rowid = result.last_insert_rowid(); let last_insert_rowid = result.last_insert_rowid();
@ -38,6 +45,8 @@ impl ExecResult {
match &self.result { match &self.result {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
ExecResultHolder::SqlxMySql(result) => result.rows_affected(), ExecResultHolder::SqlxMySql(result) => result.rows_affected(),
#[cfg(feature = "sqlx-postgres")]
ExecResultHolder::SqlxPostgres { rows_affected, .. } => rows_affected.to_owned(),
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
ExecResultHolder::SqlxSqlite(result) => result.rows_affected(), ExecResultHolder::SqlxSqlite(result) => result.rows_affected(),
#[cfg(feature = "mock")] #[cfg(feature = "mock")]

View File

@ -1,4 +1,6 @@
use crate::{error::*, ActiveModelTrait, DatabaseConnection, Insert, QueryTrait, Statement}; use crate::{
error::*, ActiveModelTrait, DatabaseConnection, EntityTrait, Insert, Iterable, Statement,
};
use sea_query::InsertStatement; use sea_query::InsertStatement;
use std::future::Future; use std::future::Future;
@ -21,7 +23,19 @@ where
db: &DatabaseConnection, db: &DatabaseConnection,
) -> impl Future<Output = Result<InsertResult, DbErr>> + '_ { ) -> impl Future<Output = Result<InsertResult, DbErr>> + '_ {
// so that self is dropped before entering await // so that self is dropped before entering await
Inserter::new(self.into_query()).exec(db) let mut query = self.query;
#[cfg(feature = "sqlx-postgres")]
if let DatabaseConnection::SqlxPostgresPoolConnection(_) = db {
use sea_query::{Alias, Expr, Query};
for key in <A::Entity as EntityTrait>::PrimaryKey::iter() {
query.returning(
Query::select()
.expr_as(Expr::col(key), Alias::new("last_insert_id"))
.to_owned(),
);
}
}
Inserter::new(query).exec(db)
} }
} }
@ -41,8 +55,15 @@ impl Inserter {
// Only Statement impl Send // Only Statement impl Send
async fn exec_insert(statement: Statement, db: &DatabaseConnection) -> Result<InsertResult, DbErr> { async fn exec_insert(statement: Statement, db: &DatabaseConnection) -> Result<InsertResult, DbErr> {
let result = db.execute(statement).await?;
// TODO: Postgres instead use query_one + returning clause // TODO: Postgres instead use query_one + returning clause
let result = match db {
#[cfg(feature = "sqlx-postgres")]
DatabaseConnection::SqlxPostgresPoolConnection(conn) => {
let res = conn.query_one(statement).await?.unwrap();
crate::query_result_into_exec_result(res)?
}
_ => db.execute(statement).await?,
};
Ok(InsertResult { Ok(InsertResult {
last_insert_id: result.last_insert_id(), last_insert_id: result.last_insert_id(),
}) })

View File

@ -153,7 +153,7 @@ where
mod tests { mod tests {
use crate::entity::prelude::*; use crate::entity::prelude::*;
use crate::tests_cfg::*; use crate::tests_cfg::*;
use crate::{DbBackend, DatabaseConnection, MockDatabase, Transaction}; use crate::{DatabaseConnection, DbBackend, MockDatabase, Transaction};
use futures::TryStreamExt; use futures::TryStreamExt;
use sea_query::{Alias, Expr, SelectStatement, Value}; use sea_query::{Alias, Expr, SelectStatement, Value};

View File

@ -11,6 +11,8 @@ pub struct QueryResult {
pub(crate) enum QueryResultRow { pub(crate) enum QueryResultRow {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
SqlxMySql(sqlx::mysql::MySqlRow), SqlxMySql(sqlx::mysql::MySqlRow),
#[cfg(feature = "sqlx-postgres")]
SqlxPostgres(sqlx::postgres::PgRow),
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
SqlxSqlite(sqlx::sqlite::SqliteRow), SqlxSqlite(sqlx::sqlite::SqliteRow),
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
@ -39,6 +41,8 @@ impl fmt::Debug for QueryResultRow {
match self { match self {
#[cfg(feature = "sqlx-mysql")] #[cfg(feature = "sqlx-mysql")]
Self::SqlxMySql(row) => write!(f, "{:?}", row), Self::SqlxMySql(row) => write!(f, "{:?}", row),
#[cfg(feature = "sqlx-postgres")]
Self::SqlxPostgres(_) => panic!("QueryResultRow::SqlxPostgres cannot be inspected"),
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
Self::SqlxSqlite(_) => panic!("QueryResultRow::SqlxSqlite cannot be inspected"), Self::SqlxSqlite(_) => panic!("QueryResultRow::SqlxSqlite cannot be inspected"),
#[cfg(feature = "mock")] #[cfg(feature = "mock")]
@ -61,6 +65,12 @@ macro_rules! try_getable_all {
row.try_get(column.as_str()) row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err) .map_err(crate::sqlx_error_to_query_err)
} }
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
}
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => { QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row; use sqlx::Row;
@ -85,6 +95,75 @@ macro_rules! try_getable_all {
Err(_) => Ok(None), Err(_) => Ok(None),
} }
} }
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)),
Err(_) => Ok(None),
}
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)),
Err(_) => Ok(None),
}
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)),
Err(_) => Ok(None),
},
}
}
}
};
}
macro_rules! try_getable_unsigned {
( $type: ty ) => {
impl TryGetable for $type {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> {
let column = format!("{}{}", pre, col);
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
}
QueryResultRow::SqlxPostgres(_) => {
panic!("{} unsupported by sqlx-postgres", stringify!($type))
}
#[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row;
row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
}
#[cfg(feature = "mock")]
QueryResultRow::Mock(row) => Ok(row.try_get(column.as_str())?),
}
}
}
impl TryGetable for Option<$type> {
fn try_get(res: &QueryResult, pre: &str, col: &str) -> Result<Self, DbErr> {
let column = format!("{}{}", pre, col);
match &res.row {
#[cfg(feature = "sqlx-mysql")]
QueryResultRow::SqlxMySql(row) => {
use sqlx::Row;
match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)),
Err(_) => Ok(None),
}
}
QueryResultRow::SqlxPostgres(_) => {
panic!("{} unsupported by sqlx-postgres", stringify!($type))
}
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => { QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row; use sqlx::Row;
@ -116,6 +195,10 @@ macro_rules! try_getable_mysql {
row.try_get(column.as_str()) row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err) .map_err(crate::sqlx_error_to_query_err)
} }
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => {
panic!("{} unsupported by sqlx-postgres", stringify!($type))
}
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => { QueryResultRow::SqlxSqlite(_) => {
panic!("{} unsupported by sqlx-sqlite", stringify!($type)) panic!("{} unsupported by sqlx-sqlite", stringify!($type))
@ -138,6 +221,10 @@ macro_rules! try_getable_mysql {
Err(_) => Ok(None), Err(_) => Ok(None),
} }
} }
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(_) => {
panic!("{} unsupported by sqlx-sqlite", stringify!($type))
}
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => { QueryResultRow::SqlxSqlite(_) => {
panic!("{} unsupported by sqlx-sqlite", stringify!($type)) panic!("{} unsupported by sqlx-sqlite", stringify!($type))
@ -158,8 +245,8 @@ try_getable_all!(i8);
try_getable_all!(i16); try_getable_all!(i16);
try_getable_all!(i32); try_getable_all!(i32);
try_getable_all!(i64); try_getable_all!(i64);
try_getable_all!(u8); try_getable_unsigned!(u8);
try_getable_all!(u16); try_getable_unsigned!(u16);
try_getable_all!(u32); try_getable_all!(u32);
try_getable_mysql!(u64); try_getable_mysql!(u64);
try_getable_all!(f32); try_getable_all!(f32);
@ -188,6 +275,12 @@ impl TryGetable for Decimal {
row.try_get(column.as_str()) row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err) .map_err(crate::sqlx_error_to_query_err)
} }
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
row.try_get(column.as_str())
.map_err(crate::sqlx_error_to_query_err)
}
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => { QueryResultRow::SqlxSqlite(row) => {
use sqlx::Row; use sqlx::Row;
@ -217,6 +310,14 @@ impl TryGetable for Option<Decimal> {
Err(_) => Ok(None), Err(_) => Ok(None),
} }
} }
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use sqlx::Row;
match row.try_get(column.as_str()) {
Ok(v) => Ok(Some(v)),
Err(_) => Ok(None),
}
}
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(_) => { QueryResultRow::SqlxSqlite(_) => {
let result: Result<Decimal, _> = TryGetable::try_get(res, pre, col); let result: Result<Decimal, _> = TryGetable::try_get(res, pre, col);

View File

@ -80,32 +80,17 @@ where
/// # #[cfg(feature = "mock")] /// # #[cfg(feature = "mock")]
/// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend}; /// # use sea_orm::{error::*, tests_cfg::*, MockDatabase, Transaction, DbBackend};
/// # /// #
/// # let db = MockDatabase::new(DbBackend::Postgres) /// # let db = MockDatabase::new(DbBackend::Postgres).into_connection();
/// # .append_query_results(vec![
/// # vec![
/// # cake::Model {
/// # id: 1,
/// # name: "New York Cheese".to_owned(),
/// # },
/// # ],
/// # ])
/// # .into_connection();
/// # /// #
/// use sea_orm::{entity::*, query::*, tests_cfg::cake}; /// use sea_orm::{entity::*, query::*, tests_cfg::cake};
/// ///
/// # let _: Result<(), DbErr> = async_std::task::block_on(async { /// # let _: Result<(), DbErr> = async_std::task::block_on(async {
/// # /// #
/// assert_eq!( /// let cheese: Option<cake::Model> = cake::Entity::find().from_raw_sql(
/// cake::Entity::find().from_raw_sql( /// Statement::from_sql_and_values(
/// Statement::from_sql_and_values( /// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#, vec![1.into()]
/// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, vec![] /// )
/// ) /// ).one(&db).await?;
/// ).one(&db).await?,
/// Some(cake::Model {
/// id: 1,
/// name: "New York Cheese".to_owned(),
/// })
/// );
/// # /// #
/// # Ok(()) /// # Ok(())
/// # }); /// # });
@ -114,7 +99,7 @@ where
/// db.into_transaction_log(), /// db.into_transaction_log(),
/// vec![ /// vec![
/// Transaction::from_sql_and_values( /// Transaction::from_sql_and_values(
/// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake""#, vec![] /// DbBackend::Postgres, r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#, vec![1.into()]
/// ), /// ),
/// ]); /// ]);
/// ``` /// ```

View File

@ -43,6 +43,44 @@ impl FromQueryResult for JsonValue {
} }
Ok(JsonValue::Object(map)) Ok(JsonValue::Object(map))
} }
#[cfg(feature = "sqlx-postgres")]
QueryResultRow::SqlxPostgres(row) => {
use serde_json::json;
use sqlx::{Column, Postgres, Row, Type};
let mut map = Map::new();
for column in row.columns() {
let col = if !column.name().starts_with(pre) {
continue;
} else {
column.name().replacen(pre, "", 1)
};
let col_type = column.type_info();
macro_rules! match_postgres_type {
( $type: ty ) => {
if <$type as Type<Postgres>>::type_info().eq(col_type) {
map.insert(
col.to_owned(),
json!(res.try_get::<Option<$type>>(pre, &col)?),
);
continue;
}
};
}
match_postgres_type!(bool);
match_postgres_type!(i8);
match_postgres_type!(i16);
match_postgres_type!(i32);
match_postgres_type!(i64);
// match_postgres_type!(u8); // unsupported by SQLx Postgres
// match_postgres_type!(u16); // unsupported by SQLx Postgres
match_postgres_type!(u32);
// match_postgres_type!(u64); // unsupported by SQLx Postgres
match_postgres_type!(f32);
match_postgres_type!(f64);
match_postgres_type!(String);
}
Ok(JsonValue::Object(map))
}
#[cfg(feature = "sqlx-sqlite")] #[cfg(feature = "sqlx-sqlite")]
QueryResultRow::SqlxSqlite(row) => { QueryResultRow::SqlxSqlite(row) => {
use serde_json::json; use serde_json::json;

View File

@ -20,4 +20,4 @@ pub use select::*;
pub use traits::*; pub use traits::*;
pub use update::*; pub use update::*;
pub use crate::{Statement, InsertResult, UpdateResult}; pub use crate::{InsertResult, Statement, UpdateResult, Value, Values};

99
tests/pg_tests.rs Normal file
View File

@ -0,0 +1,99 @@
use sea_orm::{
entity::prelude::*, Database, DatabaseBackend, DatabaseConnection, DbErr, ExecResult, Set,
Statement,
};
pub mod bakery_chain;
pub use bakery_chain::*;
use sea_query::{ColumnDef, TableCreateStatement};
// cargo test --test pg_tests -- --nocapture
#[async_std::test]
async fn main() {
let base_url = "postgres://root:root@localhost";
let db_name = "bakery_chain_schema_crud_tests";
let db = setup(base_url, db_name).await;
setup_schema(&db).await;
create_entities(&db).await;
}
pub async fn setup(base_url: &str, db_name: &str) -> DatabaseConnection {
let url = format!("{}/postgres", base_url);
let db = Database::connect(&url).await.unwrap();
let _drop_db_result = db
.execute(Statement::from_string(
DatabaseBackend::Postgres,
format!("DROP DATABASE IF EXISTS \"{}\";", db_name),
))
.await
.unwrap();
let _create_db_result = db
.execute(Statement::from_string(
DatabaseBackend::Postgres,
format!("CREATE DATABASE \"{}\";", db_name),
))
.await
.unwrap();
let url = format!("{}/{}", base_url, db_name);
Database::connect(&url).await.unwrap()
}
async fn setup_schema(db: &DatabaseConnection) {
assert!(create_bakery_table(db).await.is_ok());
}
async fn create_table(
db: &DatabaseConnection,
stmt: &TableCreateStatement,
) -> Result<ExecResult, DbErr> {
let builder = db.get_database_backend();
db.execute(builder.build(stmt)).await
}
pub async fn create_bakery_table(db: &DatabaseConnection) -> Result<ExecResult, DbErr> {
let stmt = sea_query::Table::create()
.table(bakery::Entity)
.if_not_exists()
.col(
ColumnDef::new(bakery::Column::Id)
.integer()
.not_null()
.auto_increment()
.primary_key(),
)
.col(ColumnDef::new(bakery::Column::Name).string())
.col(ColumnDef::new(bakery::Column::ProfitMargin).double())
.to_owned();
create_table(db, &stmt).await
}
async fn create_entities(db: &DatabaseConnection) {
test_create_bakery(db).await;
}
pub async fn test_create_bakery(db: &DatabaseConnection) {
let seaside_bakery = bakery::ActiveModel {
name: Set("SeaSide Bakery".to_owned()),
profit_margin: Set(10.4),
..Default::default()
};
let res = Bakery::insert(seaside_bakery)
.exec(db)
.await
.expect("could not insert bakery");
let bakery = Bakery::find_by_id(res.last_insert_id)
.one(db)
.await
.expect("could not find bakery");
assert!(bakery.is_some());
let bakery_model = bakery.unwrap();
assert_eq!(bakery_model.name, "SeaSide Bakery");
assert_eq!(bakery_model.profit_margin, 10.4);
}