-
Notifications
You must be signed in to change notification settings - Fork 1.4k
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
Proposal: Add native support to bitstring #4328
Proposal: Add native support to bitstring #4328
Conversation
We will be happy to add this but I believe your first challenge will be adding support for bit strings in Postgrex itself, unless it already works? :) |
I'm currently using a custom Ecto type with Postgrex, which does pretty much what I've already implemented in this PR, and everything works happily :D If you agree, I can go on with this PR and make a sample project to end-to-end test this feature with Postgresx. |
For testing, please add some integration tests in the |
Great! Thank you. |
This is the |
d5b6c97
to
8431ec2
Compare
It's looking good, thank you. I think only a couple things are missing: Ecto
Ecto SQL
|
The integration tests can go here: https://github.com/elixir-ecto/ecto/blob/master/integration_test/cases/type.exs |
I've just added the pattern for I'll then add support for bitwise functions and integration tests. Thank you for your support :) really appreciated. |
@Gigitsu Could you please revert the last commit? We wouldn't add arbitrary casting with size for any type. I think we would potentially add a parameterized type for bitstrings where the size is the parameter. But also please hold off on that until Jose has time to catch up with our conversations. Thank you. |
Everything reverted also here! |
@josevalim there was one other part we were discussing in the ecto_sql PR. it was how to handle literals. one suggestion was to handle it in the builder doing something like this @always_tagged [:bitstring]
defp do_literal(value, _, current) when current in @always_tagged do
type =
if current == :bitstring do
quote do: if is_binary(unquote(value)), do: :binary, else: :bitstring
else
current
end
{:%, [], [Ecto.Query.Tagged, {:%{}, [], [value: value, type: type]}]}
end and the other suggestion was to handle it in the adapter. Do you have any preference? |
Let’s handle it in Ecto, I think it would be wrong to say it is a binary when it is actually a bitstring. |
@josevalim I've removed every implementation of bitwise operators and added support to bitstring in migrations too both here and it the I'm also trying to implement integration tests but I could use some help here. I've managed to add a bitstring test that works for Postgres, but it seems that I'm having some trouble making it work for MySQL. |
@Gigitsu Are you able to rebase on master and resolve the conflicts? We'll be better able to help if we can see the test results here. Thank you. |
This reverts commit 3d6cc07.
Hi @josevalim @greg-rychlewski , I've updated the Earthfile to make it compatible with ARM (Apple Silicon in my case) CPUs. However, I only have experience with Docker and haven't used Earthly before, so please take a look at it. |
To correctly tag bitstring literal, I have two possible ways:
# Tagged
def quoted_type({:<<>>, _, expr}, _vars), do: is_quoted_binary(expr) && :binary || :bitstring
def quoted_type({:type, _, [_, type]}, _vars), do: type
defp is_quoted_binary(args) when is_list(args) do
Enum.all?(args, fn
{:"::", _, [_, size]} -> rem(size, 8) == 0
{:"::", _, [_, {spec, _, size}]} when spec in [:size] -> rem(size, 8) == 0
_ -> true
end)
end but I need to know every possible size specifier that can produce a bitstring that is not a binary
# Tagged
def quoted_type({:<<>>, _, _} = expr, _vars), do: is_quoted_binary(expr) && :binary || :bitstring
def quoted_type({:type, _, [_, type]}, _vars), do: type
defp is_quoted_binary(expr), do: is_binary(Code.eval_quoted(expr)) Option 2 is easier and safer than option 1, but I'm unsure if there could be any issues when evaluating the code. Option 1, on the other hand, is harder to implement. @josevalim @greg-rychlewski I could really use a second opinion on this :) |
Hi @Gigitsu , The latest suggestion I had was to do something like this #4328 (comment). WDYT? |
Sorry, I forgot about this :( So I don't have a strong preference for any of the solutions, though. Let's wait for @josevalim. Thank you @greg-rychlewski |
Maybe this is a bad idea, but we can further simplify things by always returning a third value (e.g. def quoted_type({:<<>>, _, expr}, _vars), do: :binary_or_bitstring and then check if the literal is a binary or a bitstring in defp expr(%Ecto.Query.Tagged{value: binary, type: type}, _sources, _query)
when type in [:binary_or_bitstring, :binary] and is_binary(binary) do
["'\\x", Base.encode16(binary, case: :lower) | "'::bytea"]
end
defp expr(%Ecto.Query.Tagged{value: bitstring, type: type}, _sources, _query)
when type in [:binary_or_bitstring, :bitstring] and is_bitstring(bitstring) do
bitstring_literal(bitstring)
end In this way, we can avoid double-checking and postpone it until necessary, and the user is still able to explicitly tag a literal with either |
This is a very cheap check, so we can check it more than once. But you are right, at compile-time we don't know the type, we need to move it to runtime. Although I would say at compile-time it should say |
I think we want to move the conversion from the adapters to Ecto similar to this: #4328 (comment) But if I understand Jose right, he wanted |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you @Gigitsu! @greg-rychlewski, please ship it if you are also happy!
@josevalim Sorry just wanted to confirm one thing before shipping. Did you want to send non-binary bitstring literals to the adapters as |
There's a couple reasons I prefer doing it in Ecto:
defp expr(%Ecto.Query.Tagged{value: binary, type: :binary}, _sources, _query)
when is_binary(binary) do
["'\\x", Base.encode16(binary, case: :lower) | "'::bytea"]
end
defp expr(%Ecto.Query.Tagged{value: bitstring, type: type}, _sources, _query)
when type in ~w(bitstring binary)a and is_bitstring(bitstring) do
bitstring_literal(bitstring)
end |
If we can do it in Ecto, it would be preferable. Perhaps during normalization? |
We could probably do it here, yeah: https://github.com/elixir-ecto/ecto/blob/master/lib/ecto/query/planner.ex#L1333 Though is the reason not to do something like this in the builder because the exact type might be needed somewhere else when building the query? defp do_literal(value, _, current) when current in @always_tagged do
type = quote do: if is_binary(unquote(value)), do: :binary, else: :bitstring
{:%, [], [Ecto.Query.Tagged, {:%{}, [], [value: value, type: type]}]}
end |
The do_literal approach may work too. |
@Gigitsu Sorry for all the back and forth. It's your choice:
|
@greg-rychlewski no problem, I can do it in this PR. I'm just not sure which is the best approach but you know way better than me the Ecto internals so I trust your judgment. |
thanks @Gigitsu! I'm updated the ecto_sql pointer and will merge this. once it's merged could you please have the ecto sql PR point to the latest commit on Ecto and we will merge it. |
Sure!! Thank you |
Recently, I had the need to store a variable-size bitstring in a PostgreSQL database. I was surprised to find that there isn't native support in Ecto for this data type, and the
:string
type doesn't work for me since I need to store a string where the number of bits is not a multiple of 8.This PR is currently just a draft, but if this could be a desired feature, I would be happy to implement the entire feature set, including an
Ecto.Query
set of bitwise operators to work with bits and a correspondingEcto
schema type.