@@ -671,4 +671,336 @@ defmodule EctoLibSql.TypeEncodingImplementationTest do
671671 assert count >= 1
672672 end
673673 end
674+
675+ describe "float/real field encoding" do
676+ setup do
677+ SQL . query! ( TestRepo , """
678+ CREATE TABLE IF NOT EXISTS test_types (
679+ id INTEGER PRIMARY KEY AUTOINCREMENT,
680+ real_col REAL
681+ )
682+ """ )
683+
684+ on_exit ( fn ->
685+ SQL . query! ( TestRepo , "DROP TABLE IF EXISTS test_types" )
686+ end )
687+
688+ :ok
689+ end
690+
691+ test "positive float parameter encoding" do
692+ float_val = 3.14159
693+
694+ result = SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ float_val ] )
695+ assert result . num_rows == 1
696+
697+ result = SQL . query! ( TestRepo , "SELECT real_col FROM test_types ORDER BY id DESC LIMIT 1" )
698+ assert [ [ stored ] ] = result . rows
699+ # Floating point comparison allows small precision differences
700+ assert abs ( stored - float_val ) < 0.00001
701+ end
702+
703+ test "negative float parameter encoding" do
704+ float_val = - 2.71828
705+
706+ result = SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ float_val ] )
707+ assert result . num_rows == 1
708+
709+ result = SQL . query! ( TestRepo , "SELECT real_col FROM test_types ORDER BY id DESC LIMIT 1" )
710+ assert [ [ stored ] ] = result . rows
711+ assert abs ( stored - float_val ) < 0.00001
712+ end
713+
714+ test "very small float" do
715+ float_val = 0.0000001
716+
717+ result = SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ float_val ] )
718+ assert result . num_rows == 1
719+
720+ result = SQL . query! ( TestRepo , "SELECT real_col FROM test_types ORDER BY id DESC LIMIT 1" )
721+ assert [ [ stored ] ] = result . rows
722+ assert is_float ( stored )
723+ end
724+
725+ test "very large float" do
726+ float_val = 1.23456789e10
727+
728+ result = SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ float_val ] )
729+ assert result . num_rows == 1
730+
731+ result = SQL . query! ( TestRepo , "SELECT real_col FROM test_types ORDER BY id DESC LIMIT 1" )
732+ assert [ [ stored ] ] = result . rows
733+ assert is_float ( stored )
734+ assert stored > 1.0e9
735+ end
736+
737+ test "float zero" do
738+ result = SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ 0.0 ] )
739+ assert result . num_rows == 1
740+
741+ result = SQL . query! ( TestRepo , "SELECT real_col FROM test_types WHERE real_col = ?" , [ 0.0 ] )
742+ assert [ [ stored ] ] = result . rows
743+ assert stored == 0.0
744+ end
745+
746+ test "float in WHERE clause comparison" do
747+ SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ 1.5 ] )
748+ SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ 2.7 ] )
749+ SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ 0.8 ] )
750+
751+ result =
752+ SQL . query! ( TestRepo , "SELECT COUNT(*) FROM test_types WHERE real_col > ?" , [ 1.0 ] )
753+
754+ assert [ [ count ] ] = result . rows
755+ assert count >= 2
756+ end
757+
758+ test "float in aggregate functions" do
759+ SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ 1.5 ] )
760+ SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ 2.5 ] )
761+ SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ 3.5 ] )
762+
763+ # SUM aggregate
764+ result = SQL . query! ( TestRepo , "SELECT SUM(real_col) FROM test_types" )
765+ assert [ [ sum ] ] = result . rows
766+ assert abs ( sum - 7.5 ) < 0.001
767+
768+ # AVG aggregate
769+ result = SQL . query! ( TestRepo , "SELECT AVG(real_col) FROM test_types" )
770+ assert [ [ avg ] ] = result . rows
771+ assert abs ( avg - 2.5 ) < 0.001
772+
773+ # COUNT still works
774+ result = SQL . query! ( TestRepo , "SELECT COUNT(*) FROM test_types" )
775+ assert [ [ 3 ] ] = result . rows
776+ end
777+ end
778+
779+ describe "NULL/nil edge cases" do
780+ setup do
781+ SQL . query! ( TestRepo , """
782+ CREATE TABLE IF NOT EXISTS test_types (
783+ id INTEGER PRIMARY KEY AUTOINCREMENT,
784+ int_col INTEGER,
785+ real_col REAL,
786+ text_col TEXT
787+ )
788+ """ )
789+
790+ on_exit ( fn ->
791+ SQL . query! ( TestRepo , "DROP TABLE IF EXISTS test_types" )
792+ end )
793+
794+ :ok
795+ end
796+
797+ test "NULL in SUM aggregate returns NULL" do
798+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ 10 ] )
799+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ nil ] )
800+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ 20 ] )
801+
802+ result = SQL . query! ( TestRepo , "SELECT SUM(int_col) FROM test_types" )
803+ assert [ [ sum ] ] = result . rows
804+ # SUM ignores NULLs, so should be 30
805+ assert sum == 30
806+ end
807+
808+ test "NULL in AVG aggregate is ignored" do
809+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ 10 ] )
810+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ nil ] )
811+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ 20 ] )
812+
813+ result = SQL . query! ( TestRepo , "SELECT AVG(int_col) FROM test_types" )
814+ assert [ [ avg ] ] = result . rows
815+ # AVG ignores NULLs, so should be 15 (30/2)
816+ assert avg == 15
817+ end
818+
819+ test "COUNT with NULL values" do
820+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ 10 ] )
821+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ nil ] )
822+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ 20 ] )
823+
824+ # COUNT(*) counts all rows
825+ result = SQL . query! ( TestRepo , "SELECT COUNT(*) FROM test_types" )
826+ assert [ [ 3 ] ] = result . rows
827+
828+ # COUNT(column) ignores NULLs
829+ result = SQL . query! ( TestRepo , "SELECT COUNT(int_col) FROM test_types" )
830+ assert [ [ 2 ] ] = result . rows
831+ end
832+
833+ test "COALESCE with NULL values" do
834+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col, text_col) VALUES (?, ?)" , [
835+ nil ,
836+ "default"
837+ ] )
838+
839+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col, text_col) VALUES (?, ?)" , [
840+ 42 ,
841+ "value"
842+ ] )
843+
844+ result = SQL . query! ( TestRepo , "SELECT COALESCE(int_col, 0) FROM test_types ORDER BY id" )
845+ assert [ [ 0 ] , [ 42 ] ] = result . rows
846+ end
847+
848+ test "NULL in compound WHERE clause" do
849+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col, text_col) VALUES (?, ?)" , [ 10 , "a" ] )
850+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col, text_col) VALUES (?, ?)" , [ nil , "b" ] )
851+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col, text_col) VALUES (?, ?)" , [ 20 , nil ] )
852+
853+ # Find rows where int_col is NULL
854+ result = SQL . query! ( TestRepo , "SELECT COUNT(*) FROM test_types WHERE int_col IS NULL" )
855+ assert [ [ 1 ] ] = result . rows
856+
857+ # Find rows where text_col is NOT NULL
858+ result = SQL . query! ( TestRepo , "SELECT COUNT(*) FROM test_types WHERE text_col IS NOT NULL" )
859+ assert [ [ 2 ] ] = result . rows
860+
861+ # Compound condition with NULL
862+ result =
863+ SQL . query! (
864+ TestRepo ,
865+ "SELECT COUNT(*) FROM test_types WHERE int_col IS NOT NULL AND text_col IS NOT NULL"
866+ )
867+
868+ assert [ [ 1 ] ] = result . rows
869+ end
870+
871+ test "NULL handling in CASE expressions" do
872+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ 10 ] )
873+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ nil ] )
874+
875+ result =
876+ SQL . query! (
877+ TestRepo ,
878+ "SELECT CASE WHEN int_col IS NULL THEN 'empty' ELSE 'has value' END FROM test_types ORDER BY id"
879+ )
880+
881+ assert [ [ "has value" ] , [ "empty" ] ] = result . rows
882+ end
883+
884+ test "NULL in ORDER BY" do
885+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col, text_col) VALUES (?, ?)" , [ 30 , "c" ] )
886+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col, text_col) VALUES (?, ?)" , [ nil , "a" ] )
887+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col, text_col) VALUES (?, ?)" , [ 10 , "b" ] )
888+
889+ # ORDER BY with NULLs (NULLs sort first in SQLite)
890+ result = SQL . query! ( TestRepo , "SELECT int_col FROM test_types ORDER BY int_col" )
891+ assert [ [ nil ] , [ 10 ] , [ 30 ] ] = result . rows
892+ end
893+
894+ test "NULL with DISTINCT" do
895+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ 10 ] )
896+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ nil ] )
897+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ 10 ] )
898+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ nil ] )
899+
900+ result = SQL . query! ( TestRepo , "SELECT DISTINCT int_col FROM test_types ORDER BY int_col" )
901+ assert [ [ nil ] , [ 10 ] ] = result . rows
902+ end
903+ end
904+
905+ describe "type coercion edge cases" do
906+ setup do
907+ SQL . query! ( TestRepo , """
908+ CREATE TABLE IF NOT EXISTS test_types (
909+ id INTEGER PRIMARY KEY AUTOINCREMENT,
910+ int_col INTEGER,
911+ text_col TEXT,
912+ real_col REAL
913+ )
914+ """ )
915+
916+ on_exit ( fn ->
917+ SQL . query! ( TestRepo , "DROP TABLE IF EXISTS test_types" )
918+ end )
919+
920+ :ok
921+ end
922+
923+ test "string that looks like number in text column" do
924+ result = SQL . query! ( TestRepo , "INSERT INTO test_types (text_col) VALUES (?)" , [ "12345" ] )
925+ assert result . num_rows == 1
926+
927+ result = SQL . query! ( TestRepo , "SELECT text_col FROM test_types" )
928+ assert [ [ "12345" ] ] = result . rows
929+ end
930+
931+ test "empty string vs NULL distinction" do
932+ SQL . query! ( TestRepo , "INSERT INTO test_types (text_col) VALUES (?)" , [ "" ] )
933+ SQL . query! ( TestRepo , "INSERT INTO test_types (text_col) VALUES (?)" , [ nil ] )
934+
935+ # Empty string is not NULL
936+ result = SQL . query! ( TestRepo , "SELECT COUNT(*) FROM test_types WHERE text_col = ''" )
937+ assert [ [ 1 ] ] = result . rows
938+
939+ # NULL is NULL
940+ result = SQL . query! ( TestRepo , "SELECT COUNT(*) FROM test_types WHERE text_col IS NULL" )
941+ assert [ [ 1 ] ] = result . rows
942+ end
943+
944+ test "zero vs NULL in numeric columns" do
945+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ 0 ] )
946+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ nil ] )
947+
948+ # Zero is not NULL
949+ result = SQL . query! ( TestRepo , "SELECT COUNT(*) FROM test_types WHERE int_col = ?" , [ 0 ] )
950+ assert [ [ 1 ] ] = result . rows
951+
952+ # NULL is NULL
953+ result = SQL . query! ( TestRepo , "SELECT COUNT(*) FROM test_types WHERE int_col IS NULL" )
954+ assert [ [ 1 ] ] = result . rows
955+ end
956+
957+ test "type affinity: integer stored in text column" do
958+ # SQLite has type affinity but is lenient
959+ result = SQL . query! ( TestRepo , "INSERT INTO test_types (text_col) VALUES (?)" , [ 123 ] )
960+ assert result . num_rows == 1
961+
962+ result = SQL . query! ( TestRepo , "SELECT text_col FROM test_types" )
963+ [ [ stored ] ] = result . rows
964+ # SQLite stores it, but type depends on what was passed
965+ assert stored == 123 or stored == "123"
966+ end
967+
968+ test "float precision in arithmetic" do
969+ SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ 0.1 ] )
970+ SQL . query! ( TestRepo , "INSERT INTO test_types (real_col) VALUES (?)" , [ 0.2 ] )
971+
972+ # Floating point arithmetic can have precision issues
973+ result =
974+ SQL . query! (
975+ TestRepo ,
976+ "SELECT real_col FROM test_types WHERE real_col + ? > ?" ,
977+ [ 0.1 , 0.35 ]
978+ )
979+
980+ # Due to floating point precision, this might return 0 or 1 rows
981+ # depending on exact arithmetic
982+ assert is_list ( result . rows )
983+ end
984+
985+ test "division by zero handling" do
986+ SQL . query! ( TestRepo , "INSERT INTO test_types (int_col) VALUES (?)" , [ 10 ] )
987+
988+ result = SQL . query! ( TestRepo , "SELECT int_col / 0 FROM test_types" )
989+ # SQLite returns NULL for division by zero
990+ assert [ [ nil ] ] = result . rows
991+ end
992+
993+ test "string comparison vs numeric comparison" do
994+ SQL . query! ( TestRepo , "INSERT INTO test_types (text_col) VALUES (?)" , [ "100" ] )
995+ SQL . query! ( TestRepo , "INSERT INTO test_types (text_col) VALUES (?)" , [ "20" ] )
996+
997+ # String comparison: "100" < "20" (lexicographic)
998+ result =
999+ SQL . query! ( TestRepo , "SELECT COUNT(*) FROM test_types WHERE text_col < ?" , [ "50" ] )
1000+
1001+ assert [ [ count ] ] = result . rows
1002+ # Result depends on string vs numeric comparison
1003+ assert is_integer ( count )
1004+ end
1005+ end
6741006end
0 commit comments