Skip to content

Commit c9f72d6

Browse files
author
Adnan Alhomssi
authored
Merge pull request #44 from RelationalAI/snowflake-tss
Add support for Snowflake Stages and SNOWFLAKE_FULL encryption
2 parents c2b2442 + 2a2f430 commit c9f72d6

File tree

7 files changed

+1286
-10
lines changed

7 files changed

+1286
-10
lines changed

Project.toml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
name = "RustyObjectStore"
22
uuid = "1b5eed3d-1f46-4baa-87f3-a4a892b23610"
3-
version = "0.8.2"
3+
version = "0.9.1"
44

55
[deps]
6+
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
7+
CloudBase = "85eb1798-d7c4-4918-bb13-c944d38e27ed"
68
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
9+
HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3"
710
JSON3 = "0f8b85d8-7281-11e9-16c2-39a750bddbf1"
11+
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
812
object_store_ffi_jll = "0e112785-0821-598c-8835-9f07837e8d7b"
913

1014
[compat]
@@ -16,7 +20,7 @@ ReTestItems = "1"
1620
Sockets = "1"
1721
Test = "1"
1822
julia = "1.8"
19-
object_store_ffi_jll = "0.8.2"
23+
object_store_ffi_jll = "0.9.1"
2024

2125
[extras]
2226
CloudBase = "85eb1798-d7c4-4918-bb13-c944d38e27ed"

src/RustyObjectStore.jl

Lines changed: 256 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
module RustyObjectStore
22

33
export init_object_store, get_object!, put_object, delete_object
4-
export StaticConfig, ClientOptions, Config, AzureConfig, AWSConfig
4+
export StaticConfig, ClientOptions, Config, AzureConfig, AWSConfig, SnowflakeConfig
55
export status_code, is_connection, is_timeout, is_early_eof, is_unknown, is_parse_url
66
export get_object_stream, ReadStream, finish!
77
export put_object_stream, WriteStream, cancel!, shutdown!
8-
export current_metrics
8+
export current_metrics, invalidate_config
99
export max_entries_per_chunk, ListEntry, list_objects, list_objects_stream, next_chunk!
1010

1111
using Base.Libc.Libdl: dlext
@@ -550,6 +550,156 @@ function Base.show(io::IO, conf::AWSConfig)
550550
print(io, ", ", "opts=", repr(conf.opts), ")")
551551
end
552552

553+
"""
554+
$TYPEDEF
555+
556+
Configuration for the Snowflake stage object store backend.
557+
558+
It is recommended to reuse an instance for many operations.
559+
560+
# Keyword Arguments
561+
- `stage::String`: Snowflake stage
562+
- `encryption_scheme::Option{String}`: (Optional) Encryption scheme to enforce (one of AES_128_CBC, AES_256_GCM)
563+
- `account::Option{String}`: (Optional) Snowflake account (read from SNOWFLAKE_ACCOUNT env var if missing)
564+
- `database::Option{String}`: (Optional) Snwoflake database (read from SNOWFLAKE_DATABASE env var if missing)
565+
- `schema::Option{String}`: (Optional) Snowflake schema (read from SNOWFLAKE_SCHEMA env var if missing)
566+
- `endpoint::Option{String}`: (Optional) Snowflake endpoint (read from SNOWFLAKE_ENDPOINT or SNOWFLAKE_HOST env vars if missing)
567+
- `warehouse::Option{String}`: (Optional) Snowflake warehouse
568+
- `username::Option{String}`: (Optional) Snowflake username (required for user/pass flow)
569+
- `password::Option{String}`: (Optional) Snowflake password (required for user/pass flow)
570+
- `role::Option{String}`: (Optional) Snowflake role (required for user/pass flow)
571+
- `master_token_path::Option{String}`: (Optional) Path to Snowflake master token (read from MASTER_TOKEN_PATH or defaults to `/snowflake/session/token` if missing)
572+
- `keyring_capacity::Option{Int}`: (Optional) Maximum number of keys to be kept in the in-memory keyring (key cache)
573+
- `keyring_ttl_secs::Option{Int}`: (Optional) Duration in seconds after which a key is removed from the keyring
574+
- `opts::ClientOptions`: (Optional) Client configuration options.
575+
"""
576+
struct SnowflakeConfig <: AbstractConfig
577+
stage::String
578+
encryption_scheme::Option{String}
579+
account::Option{String}
580+
database::Option{String}
581+
schema::Option{String}
582+
endpoint::Option{String}
583+
warehouse::Option{String}
584+
username::Option{String}
585+
password::Option{String}
586+
role::Option{String}
587+
master_token_path::Option{String}
588+
keyring_capacity::Option{Int}
589+
keyring_ttl_secs::Option{Int}
590+
opts::ClientOptions
591+
cached_config::Config
592+
function SnowflakeConfig(;
593+
stage::String,
594+
encryption_scheme::Option{String} = nothing,
595+
account::Option{String} = nothing,
596+
database::Option{String} = nothing,
597+
schema::Option{String} = nothing,
598+
endpoint::Option{String} = nothing,
599+
warehouse::Option{String} = nothing,
600+
username::Option{String} = nothing,
601+
password::Option{String} = nothing,
602+
role::Option{String} = nothing,
603+
master_token_path::Option{String} = nothing,
604+
keyring_capacity::Option{Int} = nothing,
605+
keyring_ttl_secs::Option{Int} = nothing,
606+
opts::ClientOptions = ClientOptions()
607+
)
608+
params = copy(opts.params)
609+
610+
params["snowflake_stage"] = stage
611+
612+
if !isnothing(encryption_scheme)
613+
params["snowflake_encryption_scheme"] = encryption_scheme
614+
end
615+
616+
if !isnothing(account)
617+
params["snowflake_account"] = account
618+
end
619+
620+
if !isnothing(database)
621+
params["snowflake_database"] = database
622+
end
623+
624+
if !isnothing(schema)
625+
params["snowflake_schema"] = schema
626+
end
627+
628+
if !isnothing(endpoint)
629+
params["snowflake_endpoint"] = endpoint
630+
end
631+
632+
if !isnothing(warehouse)
633+
params["snowflake_warehouse"] = warehouse
634+
end
635+
636+
if !isnothing(username)
637+
params["snowflake_username"] = username
638+
end
639+
640+
if !isnothing(password)
641+
params["snowflake_password"] = password
642+
end
643+
644+
if !isnothing(role)
645+
params["snowflake_role"] = role
646+
end
647+
648+
if !isnothing(master_token_path)
649+
params["snowflake_master_token_path"] = master_token_path
650+
end
651+
652+
if !isnothing(keyring_capacity)
653+
params["snowflake_keyring_capacity"] = string(keyring_capacity)
654+
end
655+
656+
if !isnothing(keyring_ttl_secs)
657+
params["snowflake_keyring_ttl_secs"] = string(keyring_ttl_secs)
658+
end
659+
660+
# All defaults for the optional values are defined on the Rust side.
661+
map!(v -> strip(v), values(params))
662+
cached_config = Config("snowflake://$(strip(stage))/", params)
663+
return new(
664+
stage,
665+
encryption_scheme,
666+
account,
667+
database,
668+
schema,
669+
endpoint,
670+
warehouse,
671+
username,
672+
password,
673+
role,
674+
master_token_path,
675+
keyring_capacity,
676+
keyring_ttl_secs,
677+
opts,
678+
cached_config
679+
)
680+
end
681+
end
682+
683+
into_config(conf::SnowflakeConfig) = conf.cached_config
684+
685+
function Base.show(io::IO, conf::SnowflakeConfig)
686+
print(io, "SnowflakeConfig("),
687+
print(io, "stage=", repr(conf.stage))
688+
@option_print(conf, encryption_scheme)
689+
@option_print(conf, account)
690+
@option_print(conf, database)
691+
@option_print(conf, schema)
692+
@option_print(conf, endpoint)
693+
@option_print(conf, warehouse)
694+
@option_print(conf, username)
695+
@option_print(conf, password, true)
696+
@option_print(conf, role)
697+
@option_print(conf, master_token_path)
698+
@option_print(conf, keyring_capacity)
699+
@option_print(conf, keyring_ttl_secs)
700+
print(io, ", ", "opts=", repr(conf.opts), ")")
701+
end
702+
553703
mutable struct Response
554704
result::Cint
555705
length::Culonglong
@@ -1755,6 +1905,105 @@ function finish!(stream::ListStream)
17551905
return true
17561906
end
17571907

1908+
mutable struct StageInfoResponseFFI
1909+
result::Cint
1910+
stage_info::Ptr{Cchar}
1911+
error_message::Ptr{Cchar}
1912+
context::Ptr{Cvoid}
1913+
1914+
StageInfoResponseFFI() = new(-1, C_NULL, C_NULL, C_NULL)
1915+
end
1916+
1917+
function current_stage_info(conf::AbstractConfig)
1918+
response = StageInfoResponseFFI()
1919+
ct = current_task()
1920+
event = Base.Event()
1921+
handle = pointer_from_objref(event)
1922+
config = into_config(conf)
1923+
while true
1924+
preserve_task(ct)
1925+
result = GC.@preserve config response event try
1926+
result = @ccall rust_lib.current_stage_info(
1927+
config::Ref{Config},
1928+
response::Ref{StageInfoResponseFFI},
1929+
handle::Ptr{Cvoid}
1930+
)::Cint
1931+
1932+
wait_or_cancel(event, response)
1933+
1934+
result
1935+
finally
1936+
unpreserve_task(ct)
1937+
end
1938+
1939+
if result == 2
1940+
# backoff
1941+
sleep(0.01)
1942+
continue
1943+
end
1944+
1945+
# No need to destroy_cstring(response.stage_info) in case of errors here
1946+
@throw_on_error(response, "current_stage_info", GetException)
1947+
1948+
info_string = unsafe_string(response.stage_info)
1949+
@ccall rust_lib.destroy_cstring(response.stage_info::Ptr{Cchar})::Cint
1950+
1951+
stage_info = JSON3.read(info_string, Dict{String, String})
1952+
return stage_info
1953+
end
1954+
end
1955+
1956+
"""
1957+
invalidate_config(conf::Option{AbstractConfig}) -> Bool
1958+
1959+
Invalidates the specified config (or all if no config is provided) in the Rust
1960+
config cache. This is useful to mitigate test interference.
1961+
1962+
# Arguments
1963+
- `conf::AbstractConfig`: (Optional) The config to be invalidated.
1964+
"""
1965+
function invalidate_config(conf::Option{AbstractConfig}=nothing)
1966+
response = Response()
1967+
ct = current_task()
1968+
event = Base.Event()
1969+
handle = pointer_from_objref(event)
1970+
while true
1971+
preserve_task(ct)
1972+
result = GC.@preserve conf response event try
1973+
result = if !isnothing(conf)
1974+
config = into_config(conf)
1975+
@ccall rust_lib.invalidate_config(
1976+
config::Ref{Config},
1977+
response::Ref{Response},
1978+
handle::Ptr{Cvoid}
1979+
)::Cint
1980+
else
1981+
@ccall rust_lib.invalidate_config(
1982+
C_NULL::Ptr{Cvoid},
1983+
response::Ref{Response},
1984+
handle::Ptr{Cvoid}
1985+
)::Cint
1986+
end
1987+
1988+
wait_or_cancel(event, response)
1989+
1990+
result
1991+
finally
1992+
unpreserve_task(ct)
1993+
end
1994+
1995+
if result == 2
1996+
# backoff
1997+
sleep(0.01)
1998+
continue
1999+
end
2000+
2001+
@throw_on_error(response, "invalidate_config", PutException)
2002+
2003+
return true
2004+
end
2005+
end
2006+
17582007
struct Metrics
17592008
live_bytes::Int64
17602009
end
@@ -1763,4 +2012,8 @@ function current_metrics()
17632012
return @ccall rust_lib.current_metrics()::Metrics
17642013
end
17652014

1766-
end # module
2015+
module Test
2016+
include("mock_server.jl")
2017+
end # Test module
2018+
2019+
end # RustyObjectStore module

0 commit comments

Comments
 (0)