Skip to content

Commit 54b776a

Browse files
committed
improvement: support starts/ends_with
1 parent 86c0b9c commit 54b776a

4 files changed

Lines changed: 208 additions & 2 deletions

File tree

lib/expr.ex

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,12 @@ defmodule AshSql.Expr do
3636
Round,
3737
StartOfDay,
3838
StringDowncase,
39+
StringEndsWith,
3940
StringJoin,
4041
StringLength,
4142
StringPosition,
4243
StringSplit,
44+
StringStartsWith,
4345
StringTrim,
4446
Today,
4547
Type
@@ -735,6 +737,200 @@ defmodule AshSql.Expr do
735737
)
736738
end
737739

740+
defp default_dynamic_expr(
741+
query,
742+
%StringStartsWith{
743+
arguments: [left, %Ash.CiString{} = right],
744+
embedded?: pred_embedded?
745+
},
746+
bindings,
747+
embedded?,
748+
acc,
749+
type
750+
) do
751+
if bindings.sql_behaviour.ilike?() do
752+
text = escape_starts_with(right.string)
753+
754+
{left, acc} =
755+
AshSql.Expr.dynamic_expr(
756+
query,
757+
left,
758+
set_location(bindings, :sub_expr),
759+
pred_embedded? || embedded?,
760+
:string,
761+
acc
762+
)
763+
764+
{Ecto.Query.dynamic(ilike(^left, ^text)), acc}
765+
else
766+
do_dynamic_expr(
767+
query,
768+
%Fragment{
769+
embedded?: pred_embedded?,
770+
arguments: [
771+
raw: "#{bindings.sql_behaviour.strpos_function()}((",
772+
expr: left,
773+
raw: "), (",
774+
expr: right,
775+
raw: ")) = 1"
776+
]
777+
},
778+
bindings,
779+
embedded?,
780+
acc,
781+
type
782+
)
783+
end
784+
end
785+
786+
defp default_dynamic_expr(
787+
query,
788+
%StringStartsWith{arguments: [left, right], embedded?: pred_embedded?},
789+
bindings,
790+
embedded?,
791+
acc,
792+
_type
793+
)
794+
when is_binary(right) do
795+
text = escape_starts_with(right)
796+
797+
{left, acc} =
798+
AshSql.Expr.dynamic_expr(
799+
query,
800+
left,
801+
set_location(bindings, :sub_expr),
802+
pred_embedded? || embedded?,
803+
:string,
804+
acc
805+
)
806+
807+
{Ecto.Query.dynamic(like(^left, ^text)), acc}
808+
end
809+
810+
defp default_dynamic_expr(
811+
query,
812+
%StringStartsWith{arguments: [left, right], embedded?: pred_embedded?},
813+
bindings,
814+
embedded?,
815+
acc,
816+
type
817+
) do
818+
do_dynamic_expr(
819+
query,
820+
%Fragment{
821+
embedded?: pred_embedded?,
822+
arguments: [
823+
raw: "(#{bindings.sql_behaviour.strpos_function()}((",
824+
expr: left,
825+
raw: "), (",
826+
expr: right,
827+
raw: ")) = 1)"
828+
]
829+
},
830+
bindings,
831+
embedded?,
832+
acc,
833+
type
834+
)
835+
end
836+
837+
defp default_dynamic_expr(
838+
query,
839+
%StringEndsWith{
840+
arguments: [left, %Ash.CiString{} = right],
841+
embedded?: pred_embedded?
842+
},
843+
bindings,
844+
embedded?,
845+
acc,
846+
type
847+
) do
848+
if bindings.sql_behaviour.ilike?() do
849+
text = escape_ends_with(right.string)
850+
851+
{left, acc} =
852+
AshSql.Expr.dynamic_expr(
853+
query,
854+
left,
855+
set_location(bindings, :sub_expr),
856+
pred_embedded? || embedded?,
857+
:string,
858+
acc
859+
)
860+
861+
{Ecto.Query.dynamic(ilike(^left, ^text)), acc}
862+
else
863+
do_dynamic_expr(
864+
query,
865+
%Fragment{
866+
embedded?: pred_embedded?,
867+
arguments: [
868+
raw: "(",
869+
expr: left,
870+
raw: ") LIKE ('%' || ",
871+
expr: right,
872+
raw: ")"
873+
]
874+
},
875+
bindings,
876+
embedded?,
877+
acc,
878+
type
879+
)
880+
end
881+
end
882+
883+
defp default_dynamic_expr(
884+
query,
885+
%StringEndsWith{arguments: [left, right], embedded?: pred_embedded?},
886+
bindings,
887+
embedded?,
888+
acc,
889+
_type
890+
)
891+
when is_binary(right) do
892+
text = escape_ends_with(right)
893+
894+
{left, acc} =
895+
AshSql.Expr.dynamic_expr(
896+
query,
897+
left,
898+
set_location(bindings, :sub_expr),
899+
pred_embedded? || embedded?,
900+
:string,
901+
acc
902+
)
903+
904+
{Ecto.Query.dynamic(like(^left, ^text)), acc}
905+
end
906+
907+
defp default_dynamic_expr(
908+
query,
909+
%StringEndsWith{arguments: [left, right], embedded?: pred_embedded?},
910+
bindings,
911+
embedded?,
912+
acc,
913+
type
914+
) do
915+
do_dynamic_expr(
916+
query,
917+
%Fragment{
918+
embedded?: pred_embedded?,
919+
arguments: [
920+
raw: "((",
921+
expr: left,
922+
raw: ") LIKE ('%' || (",
923+
expr: right,
924+
raw: ")))"
925+
]
926+
},
927+
bindings,
928+
embedded?,
929+
acc,
930+
type
931+
)
932+
end
933+
738934
defp default_dynamic_expr(
739935
query,
740936
%Has{arguments: [left, right], embedded?: pred_embedded?},
@@ -3628,6 +3824,14 @@ defmodule AshSql.Expr do
36283824
"%" <> String.replace(text, ~r/([\%_])/u, "\\\\\\0") <> "%"
36293825
end
36303826

3827+
defp escape_starts_with(text) do
3828+
String.replace(text, ~r/([\%_])/u, "\\\\\\0") <> "%"
3829+
end
3830+
3831+
defp escape_ends_with(text) do
3832+
"%" <> String.replace(text, ~r/([\%_])/u, "\\\\\\0")
3833+
end
3834+
36313835
defp determine_types(sql_behaviour, mod, args, returns) do
36323836
{types, new_returns} =
36333837
if function_exported?(sql_behaviour, :determine_types, 3) do

lib/join.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ defmodule AshSql.Join do
3232
Ash.Query.Function.Minus,
3333
Ash.Query.Function.Round,
3434
Ash.Query.Function.StringDowncase,
35+
Ash.Query.Function.StringEndsWith,
3536
Ash.Query.Function.StringJoin,
3637
Ash.Query.Function.StringLength,
3738
Ash.Query.Function.StringSplit,
39+
Ash.Query.Function.StringStartsWith,
3840
Ash.Query.Function.StringTrim
3941
]
4042

mix.exs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ defmodule AshSql.MixProject do
8080
# Run "mix help deps" to learn about dependencies.
8181
defp deps do
8282
[
83-
{:ash, ash_version("~> 3.24")},
83+
{:ash, ash_version("~> 3.24 and >= 3.24.5")},
8484
{:ecto_sql, "~> 3.9"},
8585
{:ecto, "~> 3.13 and >= 3.13.4"},
8686
# dev/test dependencies

mix.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
%{
2-
"ash": {:hex, :ash, "3.24.3", "f7280a43c5e64f769a450f3dd59ace6dcd73edcdd0de7599815b1b31f59292fb", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.6.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1022f8c549632137cbc8956f07bb4981405297f5abe7a752b4dffac175c3381"},
2+
"ash": {:hex, :ash, "3.24.5", "85ed4d834c2fc52a2e5f19b6cdb3d875bea62d32e0d9838f5a8c8d74a6d653a9", [:mix], [{:crux, ">= 0.1.2 and < 1.0.0-0", [hex: :crux, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.7", [hex: :ecto, repo: "hexpm", optional: false]}, {:ets, "~> 0.8", [hex: :ets, repo: "hexpm", optional: false]}, {:igniter, ">= 0.6.29 and < 1.0.0-0", [hex: :igniter, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: false]}, {:picosat_elixir, "~> 0.2", [hex: :picosat_elixir, repo: "hexpm", optional: true]}, {:plug, ">= 0.0.0", [hex: :plug, repo: "hexpm", optional: true]}, {:reactor, "~> 1.0", [hex: :reactor, repo: "hexpm", optional: false]}, {:simple_sat, ">= 0.1.1 and < 1.0.0-0", [hex: :simple_sat, repo: "hexpm", optional: true]}, {:spark, ">= 2.6.0", [hex: :spark, repo: "hexpm", optional: false]}, {:splode, "~> 0.3", [hex: :splode, repo: "hexpm", optional: false]}, {:stream_data, "~> 1.0", [hex: :stream_data, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.1", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "596e7da2f488f6b7b43c76be3643cb3fb3ef2d82908d9d79a65b25ab3cf5a4fb"},
33
"benchee": {:hex, :benchee, "1.5.0", "4d812c31d54b0ec0167e91278e7de3f596324a78a096fd3d0bea68bb0c513b10", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.1", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "5b075393aea81b8ae74eadd1c28b1d87e8a63696c649d8293db7c4df3eb67535"},
44
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
55
"credo": {:hex, :credo, "1.7.18", "5c5596bf7aedf9c8c227f13272ac499fe8eae6237bd326f2f07dfc173786f042", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "a189d164685fd945809e862fe76a7420c4398fa288d76257662aecb909d6b3e5"},

0 commit comments

Comments
 (0)