use oxigdal_postgis::copy_binary::{COPY_BINARY_SIGNATURE, CopyBinaryEncoder, ewkb_from_wkb};
fn little_endian_wkb_point(x: f64, y: f64) -> Vec<u8> {
let mut wkb = Vec::with_capacity(21);
wkb.push(0x01); wkb.extend_from_slice(&1u32.to_le_bytes()); wkb.extend_from_slice(&x.to_le_bytes());
wkb.extend_from_slice(&y.to_le_bytes());
wkb
}
#[test]
fn test_copy_encoder_signature_bytes() {
let encoder = CopyBinaryEncoder::new();
let payload = encoder.finish();
assert_eq!(&payload[..11], ©_BINARY_SIGNATURE);
assert_eq!(&payload[..11], b"PGCOPY\n\xff\r\n\0");
}
#[test]
fn test_copy_encoder_header_flags_and_extension_zero() {
let encoder = CopyBinaryEncoder::new();
let payload = encoder.finish();
assert_eq!(&payload[11..19], &[0u8; 8]);
}
#[test]
fn test_copy_encoder_single_row_field_count() {
let mut encoder = CopyBinaryEncoder::new();
let header_len = encoder.len();
encoder.begin_row(2);
let payload = encoder.finish();
assert_eq!(&payload[header_len..header_len + 2], &[0x00, 0x02]);
}
#[test]
fn test_copy_encoder_write_field_length_prefix_big_endian() {
let mut encoder = CopyBinaryEncoder::new();
let mark = encoder.len();
encoder.write_field_bytes(&[0xAA, 0xBB]);
let payload = encoder.finish();
assert_eq!(
&payload[mark..mark + 6],
&[0x00, 0x00, 0x00, 0x02, 0xAA, 0xBB]
);
}
#[test]
fn test_copy_encoder_write_null_emits_minus_one() {
let mut encoder = CopyBinaryEncoder::new();
let mark = encoder.len();
encoder.write_null();
let payload = encoder.finish();
assert_eq!(&payload[mark..mark + 4], &[0xFF, 0xFF, 0xFF, 0xFF]);
}
#[test]
fn test_copy_encoder_finish_appends_trailer() {
let encoder = CopyBinaryEncoder::new();
let payload = encoder.finish();
assert_eq!(&payload[payload.len() - 2..], &[0xFF, 0xFF]);
}
#[test]
fn test_copy_encoder_multi_row_layout() {
let mut encoder = CopyBinaryEncoder::new();
let body_start = encoder.len();
encoder.begin_row(1);
encoder.write_field_bytes(&[0x10]);
encoder.begin_row(2);
encoder.write_null();
encoder.write_field_bytes(&[0x20, 0x21]);
let payload = encoder.finish();
let expected_body: Vec<u8> = vec![
0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x10, 0x00, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x02, 0x20, 0x21, 0xFF, 0xFF,
];
assert_eq!(&payload[body_start..], expected_body.as_slice());
}
#[test]
fn test_ewkb_from_wkb_sets_srid_flag_and_appends_srid() {
let wkb = little_endian_wkb_point(1.0, 2.0);
let ewkb = ewkb_from_wkb(&wkb, 4326).expect("ewkb conversion failed");
assert_eq!(ewkb.len(), wkb.len() + 4);
assert_eq!(ewkb[0], 0x01);
let type_word = u32::from_le_bytes([ewkb[1], ewkb[2], ewkb[3], ewkb[4]]);
assert_eq!(type_word & 0x2000_0000, 0x2000_0000);
assert_eq!(type_word & 0x0000_00FF, 1);
let srid = i32::from_le_bytes([ewkb[5], ewkb[6], ewkb[7], ewkb[8]]);
assert_eq!(srid, 4326);
}
#[test]
fn test_ewkb_from_wkb_point_round_trip_byte_layout() {
let wkb = little_endian_wkb_point(1.0, 2.0);
let ewkb = ewkb_from_wkb(&wkb, 4326).expect("ewkb conversion failed");
let mut expected = Vec::with_capacity(25);
expected.push(0x01); expected.extend_from_slice(&0x2000_0001u32.to_le_bytes()); expected.extend_from_slice(&4326i32.to_le_bytes()); expected.extend_from_slice(&1.0f64.to_le_bytes()); expected.extend_from_slice(&2.0f64.to_le_bytes());
assert_eq!(ewkb, expected);
}
#[tokio::test]
#[ignore = "requires a running PostgreSQL+PostGIS instance (set PG_TEST_URL)"]
async fn test_copy_in_binary_roundtrip_100_features() {
use oxigdal_core::vector::feature::Feature;
use oxigdal_core::vector::geometry::{Geometry, Point};
use oxigdal_postgis::{ConnectionConfig, ConnectionPool, PostGisWriter};
let conn_str = std::env::var("PG_TEST_URL")
.expect("PG_TEST_URL must be set for the live binary-COPY round-trip test");
let writer_config = ConnectionConfig::from_connection_string(&conn_str)
.expect("invalid PG_TEST_URL connection string");
let writer_pool =
ConnectionPool::new(writer_config).expect("failed to build writer connection pool");
let verify_config = ConnectionConfig::from_connection_string(&conn_str)
.expect("invalid PG_TEST_URL connection string");
let verify_pool =
ConnectionPool::new(verify_config).expect("failed to build verify connection pool");
let table = "oxigdal_copy_binary_roundtrip_test";
let mut writer = PostGisWriter::new(writer_pool, table)
.srid(4326)
.create_table(true);
writer.truncate().await.expect("truncate failed");
for i in 0..100i32 {
let point = Point::new(f64::from(i), f64::from(i) * 2.0);
let mut feature = Feature::new(Geometry::Point(point));
feature.set_property(
"idx",
oxigdal_core::vector::feature::FieldValue::Integer(i64::from(i)),
);
writer.add_to_batch(feature);
}
let written = writer.flush().await.expect("binary COPY flush failed");
assert_eq!(written, 100);
let client = verify_pool.get().await.expect("failed to get connection");
let sql = format!("SELECT COUNT(*) FROM \"{table}\"");
let row = client
.query_one(&sql, &[])
.await
.expect("count query failed");
let count: i64 = row.get(0);
assert_eq!(count, 100);
writer.truncate().await.expect("cleanup truncate failed");
}