# index.md

Learn ScyllaDB using the Cloud Database

This tutorial is a step-by-step guide to build an Media Player project connected to ScyllaDB Cloud. After completing the project, you will be able to create back-end services that share data through a ScyllaDB Cloud cluster.
# build-with-csharp.md # Quick Start: Csharp In this tutorial we’re gonna build a simple Media Player to store our songs and build playlists ## 1. Setup the Environment ### 1.1 Downloading .NET dependecies: If you don’t have csharp installed already on your machine, you can install from the following source: 1. [.NET main website](https://dotnet.microsoft.com/en-us/download/) ### 1.2 Starting the project Now with the .NET installed, let’s create a new project with the following command: ```sh dotnet new console -n media_player && cd media_player ``` ### 1.3 Setting the project dependencies First we’ll install the required package to connect to scyllaDB with the following command: ```sh dotnet add package ScyllaDBCSharpDriver ``` This package can be found at [github](https://github.com/scylladb/csharp-driver/) > Disclaimer: This package require system wide dependencies with the cassandra client, so it’s required to install on your system (or run the whole application under a docker image). You can find the installation guide at: https://cassandra.apache.org/doc/latest/cassandra/getting_started/installing.html ## 2. Connecting to the Cluster Make sure to get the right credentials on your [ScyllaDB Cloud Dashboard](https://cloud.scylladb.com/clusters) in the tab `Connect`. ```csharp using Cassandra; namespace Program; public class Program { static void Main(string[] args) { var cluster = Cluster.Builder() .AddContactPoints("node-0.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-1.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-2.aws-sa-east-1.xxx.clusters.scylla.cloud") .WithCredentials("scylla", "a-very-secure-password") .Build(); } } ``` > If the connection got refused, check if your IP Address is added into allowed IPs. ## 3. Handling Queries Using the `cassandra` package you can instantiate a session and then run fully queries. ```csharp using Cassandra; namespace Program; public class Program { static void Main(string[] args) { var session = cluster.Connect(); var rs = session.Execute("SELECT address, port, connection_stage FROM system.clients LIMIT 5"); foreach (var row in rs) { Console.Write($"IP -> {row["address"]}, Port -> {row["port"]}, CS -> {row["connection_stage"]}"); } } } ``` The output should look something like: ```default IP -> 172.17.0.1, Port -> 52830, CS -> READY ``` ### 3.1 Creating a Keyspace The `keyspace` inside the ScyllaDB ecossystem can be interpreted as your `database` or `collection`. On your connection boot, you don’t need to provide it but you will use it later and also is able to create when you need. ```csharp using Cassandra; namespace Program; public class Program { static void Main(string[] args) { var cluster = Cluster.Builder() .AddContactPoints("node-0.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-1.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-2.aws-sa-east-1.xxx.clusters.scylla.cloud") .WithCredentials("scylla", "a-very-secure-password") .Build(); string keyspace = "media_player"; var session = cluster.Connect(); var ps = session.Prepare("select keyspace_name from system_schema.keyspaces WHERE keyspace_name=?"); var statement = ps.Bind(keyspace); var result = session.Execute(statement); var rows = result .GetRows() .ToList(); if(rows.Count == 0) { StringBuilder sb = new(); sb.Append($"CREATE KEYSPACE {keyspace};"); session.Execute(sb.ToString()); Console.WriteLine("Keyspace created!"); } else { Console.WriteLine("Keyspace already created!"); } // Reconnecting to the cluster with the correct keyspace session = cluster.Connect("media_player") } } ``` ### 3.2 Creating a table A table is used to store part or all the data of your app (depends on how you will build it). Remember to add your `keyspace` into your connection and let’s create a table to store our liked songs. ```csharp using Cassandra; namespace Program; public class Program { static void Main(string[] args) { var cluster = Cluster.Builder() .AddContactPoints("node-0.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-1.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-2.aws-sa-east-1.xxx.clusters.scylla.cloud") .WithCredentials("scylla", "a-very-secure-password") .Build(); string keyspace = "media_player"; string table = "playlist"; var session = cluster.Connect(keyspace); var ps = session.Prepare("select keyspace_name, table_name from system_schema.tables where keyspace_name = ? AND table_name = ?"); var statement = ps.Bind(keyspace, table); var result = session.Execute(statement); var rows = result .GetRows() .ToList(); if(rows.Count == 0) { StringBuilder sb = new(); sb.Append($"CREATE TABLE {keyspace}.{table} (id uuid, title text, album text, artist text, created_at timestamp, PRIMARY KEY(id, created_at));"); session.Execute(sb.ToString()); Console.WriteLine("Table created!"); } else { Console.WriteLine("Table already created!"); } } } ``` ### 3.3 Inserting data Now that we have the keyspace and a table inside of it, we need to bring some good songs and populate it. ```csharp using Cassandra; namespace Program; public class Program { public record Song(string Title, string Album, string Artist, DateTime CreatedAt); static void Main(string[] args) { var cluster = Cluster.Builder() .AddContactPoints("node-0.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-1.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-2.aws-sa-east-1.xxx.clusters.scylla.cloud") .WithCredentials("scylla", "a-very-secure-password") .Build(); string keyspace = "media_player"; string table = "playlist"; var session = cluster.Connect(keyspace); List songs = new() { new Song("Bohemian Rhapsody", "A night at the Opera", "Queen", DateTime.Now), new Song("Closer to the Edge", "This Is War", "Thirty Seconds to Mars", DateTime.Now), new Song("I Write Sins Not Tragedies", "A Fever You Can't Sweat Out", "Panic! at the Disco", DateTime.Now), }; var ps = session.Prepare($"INSERT INTO {table} (id, title, album, artist, created_at) VALUES (?, ?, ?, ?, ?)"); foreach(Song song in songs) { var uuid = Guid.NewGuid(); var statement = ps.Bind(uuid,song.Title, song.Album, song.Artist, song.CreatedAt); session.Execute(statement); } } } ``` ### 3.4 Reading data Since probably we added more than 3 songs into our database, let’s list it into our terminal. ```csharp using Cassandra; namespace Program; public class Program { static void Main(string[] args) { var cluster = Cluster.Builder() .AddContactPoints("node-0.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-1.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-2.aws-sa-east-1.xxx.clusters.scylla.cloud") .WithCredentials("scylla", "a-very-secure-password") .Build(); string keyspace = "media_player"; string table = "playlist"; var session = cluster.Connect(keyspace); var result = session.Execute($"SELECT id, title, album, artist FROM {table}"); var rows = result .GetRows() .ToList(); foreach(var row in rows) { Console.WriteLine($"ID: {row["id"]} | Song: {row["title"]} | Album: {row["album"]} | Artist: {row["artist"]}" ); } } } ``` The result will look like: ```default ID: 0d617157-57aa-48aa-98dc-f850b74e6aba | Song: Bohemian Rhapsody | Album: A night at the Opera | Artist: Queen ID: 1b9a90fa-ec7a-4469-babb-f375fb8635cf | Song: Closer to the Edge | Album: This Is War | Artist: Thirty Seconds to Mars ID: e59da413-d185-4065-9b7c-ae36f5203e90 | Song: I Write Sins Not Tragedies | Album: A Fever You Can't Sweat Out | Artist: Panic! at the Disco ``` ### 3.5 Updating data Ok, almost there! Now we’re going to learn about update but here’s a disclaimer: > INSERT and UPDATES are not equals! There’s a myth in ScyllaDB/Cassandra community that it’s the same for the fact that you just need the `Partition Key` and `Clustering Key` (if you have one) and query it. If you want to read more about it, [click here.](https://docs.scylladb.com/stable/using-scylla/cdc/cdc-basic-operations.html) As we can see, the `UPDATE QUERY` takes two fields on `WHERE` (PK and CK). Check the snippet below: ```csharp using Cassandra; using System.Globalization; namespace Program; public class Program { public record Song(string Title, string Album, string Artist, DateTime CreatedAt); static void Main(string[] args) { var cluster = Cluster.Builder() .AddContactPoints("node-0.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-1.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-2.aws-sa-east-1.xxx.clusters.scylla.cloud") .WithCredentials("scylla", "a-very-secure-password") .Build(); string keyspace = "media_player"; string table = "playlist"; var session = cluster.Connect(keyspace); Song song = new( "Bohemian Rhapsody Updated", "A night at the Opera Updated", "Queen Updated", DateTime.ParseExact("2023-09-16 18:22:56.397000+0000", "yyyy-MM-dd HH:mm:ss.ffffffzzz", CultureInfo.InvariantCulture)); var ps = session.Prepare($"UPDATE {keyspace}.{table} SET title = ?, album = ?, artist = ? where id = ? and created_at = ?"); var statement = ps.Bind(song.Title, song.Album, song.Artist, new Guid("0d617157-57aa-48aa-98dc-f850b74e6aba"), song.CreatedAt); session.Execute(statement); Console.WriteLine("Song Updated"); } } ``` After updated, let’s query for the ID and see the results: ```default scylla@cqlsh:media_player> select * from media_player.playlist where id = 0d617157-57aa-48aa-98dc-f850b74e6aba; id | created_at | album | artist | title --------------------------------------+---------------------------------+-------------------+-----------------+--------------------------------- 0d617157-57aa-48aa-98dc-f850b74e6aba | 2023-09-16 18:22:56.397000+0000 | 2023-09-16 18:22:56.397000+0000 | Queen Updated | Bohemian Rhapsody Updated (1 rows) ``` It only “updated” the field `title`, `album` and `artist`(that is our Clustering Key) and since we didn’t inputted the rest of the data, it will not be replicated as expected. ### 3.5 Deleting data Let’s understand what we can DELETE with this statement. There’s the normal `DELETE` statement that focus on `ROWS` and other one that delete data only from `COLUMNS` and the syntax is very similar. ```sql -- Deletes a single row DELETE FROM songs WHERE id = 0d617157-57aa-48aa-98dc-f850b74e6aba; -- Deletes a whole column DELETE artist FROM songs WHERE id = 0d617157-57aa-48aa-98dc-f850b74e6aba; ``` If you want to erase a specific column, you also should pass as parameter the `Clustering Key` and be very specific in which register you want to delete something. On the other hand, the “normal delete” just need the `Partition Key` to handle it. Just remember: if you use the statement “DELETE FROM keyspace.table_name” it will delete ALL the rows that you stored with that ID. ```csharp using Cassandra; using System.Globalization; namespace Program; public class Program { public record Song(string Title, string Album, string Artist, DateTime CreatedAt); static void Main(string[] args) { var cluster = Cluster.Builder() .AddContactPoints("node-0.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-1.aws-sa-east-1.xxx.clusters.scylla.cloud") .AddContactPoints("node-2.aws-sa-east-1.xxx.clusters.scylla.cloud") .WithCredentials("scylla", "a-very-secure-password") .Build(); string keyspace = "media_player"; string table = "playlist"; var session = cluster.Connect(keyspace); Song song = new( "Bohemian Rhapsody Updated", "A night at the Opera Updated", "Queen Updated", DateTime.ParseExact("2023-09-16 18:22:56.397000+0000", "yyyy-MM-dd HH:mm:ss.ffffffzzz", CultureInfo.InvariantCulture)); var ps = session.Prepare($"DELETE FROM {keyspace}.{table} where id = ? and created_at = ?"); var statement = ps.Bind(new Guid("0d617157-57aa-48aa-98dc-f850b74e6aba"), song.CreatedAt); session.Execute(statement); Console.WriteLine("Song deleted!"); } } ``` ## Conclusion Yay! You now have the knowledge to use the basics of ScyllaDB with Csharp. If you thinks that something can be improved, please open an issue and let’s make it happen! Did you like the content? Dont forget to star the repo and follow us on socials. # build-with-elixir.md # Quick Start: Elixir ## 1. Setup the Environment ### 1.1 Downloading Elixir and dependencies: If you don’t have Elixir and Erlang installed already on your machine, you can install from two possible sources: 1. [Elixir main website](https://elixir-lang.org/install.html) 2. [asdf](https://asdf-vm.com/guide/getting-started.html) > NOTE: After installing asdf correctly make sure to install [Erlang](https://github.com/asdf-vm/asdf-erlang) first and then [Elixir](https://github.com/asdf-vm/asdf-elixir). ### 1.2 Starting the project Now with Elixir properly installed you can create a project using [mix](https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html). To create our project just run: ```sh mix new media_player ``` ### 1.3 Setting the project dependencies Let’s do a quick change into our `mix.exs` and add our project dependencies. ```exs defp deps do [ {:decimal, "~> 1.0"}, {:xandra, "~> 0.14"} ] end ``` - [Decimal](https://hexdocs.pm/decimal/readme.html): Arbitrary precision decimal arithmetic - [Xandra](https://github.com/lexhide/xandra): Fast, simple, and robust Cassandra/ScyllaDB driver for Elixir To carry out modifications, use the module already created in `lib/media_player.ex`, as this is where we are going to carry out some modifications. > NOTE: In this tutorial we are going to prepare an interactive project so that you can perform tests using Elixir’s interactive shell. On every code update, don’t forget to restart Elixir’s interactive shell to perform the recompilation. ## 2. Connecting to the Cluster Make sure to get the right credentials on your [ScyllaDB Cloud Dashboard](https://cloud.scylladb.com/clusters) in the tab `Connect`. ```ex def start_link do options = [username: "scylla", password: "a-very-secure-password"] {:ok, cluster} = Xandra.Cluster.start_link( sync_connect: :infinity, authentication: {Xandra.Authenticator.Password, options}, nodes: [ "node-0.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-1.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-2.aws-sa-east-1.xxx.clusters.scylla.cloud" ], pool_size: 10 ) cluster end ``` When starting a cluster notice that there is an option with the name `sync_connect` being informed. This option is used to inform that we are dealing with an asynchronous connection, saying to wait the necessary time (`:infinity`) to make the complete connection of the cluster. To test our connection let’s initialize Elixir’s interactive shell: ```sh iex -S mix ``` Then you will see a screen waiting for some input, with in the bottom left corner a message that looks like `iex(1)>` (this means we are ready to test our first module). To test it now, let’s run: ```ex MediaPlayer.start_link ``` A process will be started, so the return should be nothing other than something like `#PID<0.230.0>`. Don’t worry, in the next topic we’ll run our first query and see a real result. This function will be used every time we need to start a connection link with our cluster. > If the connection got refused, check if your IP Address is added into allowed IPs. ## 3. Handling Queries With `Xandra` you can run queries and save their returns in maps, making it possible to parse this information and manipulate it as you decide. First of all, let’s create a function that will simply execute queries, receiving information from the cluster and the query to be executed as parameters. If the return is `:ok`, it means that the query executed successfully, so we return it. If the return is `:error`, it means that we had an error, so let’s inspect it. An important detail is for the address, which instead of bringing a simple text brings a tuple with four integers. ```ex def run_query(cluster, query) do case Xandra.Cluster.execute(cluster, query) do {:ok, result} -> result {:error, error} -> IO.inspect(error) end end def handling_queries do statement = "SELECT address, port, connection_stage FROM system.clients LIMIT 5;" run_query(start_link(), statement) |> Enum.each(fn %{ "address" => address, "connection_stage" => connection_stage, "port" => port } -> # `address` is a tuple of 4 integers address_formated = address |> Tuple.to_list() |> Enum.map(&Integer.to_string/1) |> Enum.join(".") IO.puts("IP -> #{address_formated}, Port -> #{port}, CS -> #{connection_stage}") end) end ``` The output should look something like: ```default IP -> 123.123.123.69, Port -> 61667, CS -> READY IP -> 123.123.123.69, Port -> 62377, CS -> AUTHENTICATING IP -> 123.123.123.69, Port -> 63221, CS -> AUTHENTICATING IP -> 123.123.123.69, Port -> 65225, CS -> READY ``` ### 3.1 Creating a Keyspace The `keyspace` inside the ScyllaDB ecossystem can be interpreted as your `database` or `collection`. On your connection boot, you don’t need to provide it but you will use it later and also is able to create when you need. ```ex def keyspace_exists?(keyspace_name) do cluster = start_link() # In this case I won't use the `run_query` function because I want to # show the possibility of using maps to bind its parameters. %Xandra.Page{} = page = Xandra.Cluster.run(cluster, fn conn -> prepared = Xandra.prepare!( conn, "SELECT * FROM system_schema.keyspaces WHERE keyspace_name = :keyspace_name;" ) Xandra.execute!(conn, prepared, %{"keyspace_name" => keyspace_name}) end) Enum.to_list(page) != [] end def create_keyspace(keyspace_name) do case keyspace_exists?(keyspace_name) do true -> IO.puts("Keyspace already exists") false -> cluster = start_link() query = "CREATE KEYSPACE IF NOT EXISTS #{keyspace_name};" run_query(cluster, query) IO.puts("Keyspace created") end end ``` To test run your interactive shell again with `iex -S mix` and then run: ```ex iex(1)> MediaPlayer.create_keyspace("media_player") ``` Done! Now your keyspace is officially created! ### 3.2 Creating a table A table is used to store part or all the data of your app (depends on how you will build it). Remember to add your `keyspace` into your connection and let’s create a table to store our liked songs. ```ex def table_exists?(keyspace_name, table_name) do cluster = start_link() %Xandra.Page{} = page = Xandra.Cluster.run(cluster, fn conn -> prepared = Xandra.prepare!( conn, "SELECT keyspace_name, table_name FROM system_schema.tables WHERE keyspace_name = :keyspace_name AND table_name = :table_name;" ) Xandra.execute!(conn, prepared, %{ "keyspace_name" => keyspace_name, "table_name" => table_name }) end) Enum.to_list(page) != [] end def create_table(keyspace_name, table_name) do case table_exists?(keyspace_name, table_name) do true -> IO.puts("Table already exists") false -> cluster = start_link() query = "CREATE TABLE IF NOT EXISTS #{keyspace_name}.#{table_name} ( id uuid, title text, album text, artist text, created_at timestamp, PRIMARY KEY (id, created_at) );" run_query(cluster, query) IO.puts("Table created") end end ``` We added a check if the table exists, passing the keyspace name and the table name as parameters, working in the same way as the keyspace check. To test your table creation, open the interactive shell again and run: ```ex iex(1)> MediaPlayer.create_table("media_player", "playlist") ``` Done! Now your playlist table is officially created! ### 3.3 Inserting data Now that we have the keyspace and a table inside of it, we need to bring some good songs and populate it. First of all, let’s add a dependency to our `mix.exs` to work with UUID, then just run `mix deps.get` to update the dependencies! ```exs {:elixir_uuid, "~> 1.2"} ``` To execute our query, let’s create another `run_query` function that will receive three parameters (the cluster, the query and the other parameters) to prepare our execution. In Elixir, we can have functions with the same name but different number of parameters, in which it will be understood that they are different functions. ```ex def run_query(cluster, query, params) do prepared = Xandra.Cluster.prepare!(cluster, query) case Xandra.Cluster.execute(cluster, prepared, params) do {:ok, result} -> result {:error, error} -> IO.inspect(error) end end def insert_songs(keyspace, table) do cluster = start_link() song_list = [ %{ id: UUID.uuid4(), title: "Getaway Car", album: "Reputation", artist: "Taylor Swift", created_at: DateTime.utc_now() }, %{ id: UUID.uuid4(), title: "Still Into You", album: "Paramore", artist: "Paramore", created_at: DateTime.utc_now() }, %{ id: UUID.uuid4(), title: "Stolen Dance", album: "Sadnecessary", artist: "Milky Chance", created_at: DateTime.utc_now() } ] Enum.each(song_list, fn %{ id: id, title: title, album: album, artist: artist, created_at: created_at } -> query = "INSERT INTO #{keyspace}.#{table} (id, title, album, artist, created_at) VALUES (?, ?, ?, ?, ?);" run_query(cluster, query, [id, title, album, artist, created_at]) end) end ``` The need to create a function was to properly prepare our query with our arguments. To test just run the interactive shell again with `iex -S mix` and run: ```ex iex(1)> MediaPlayer.insert_songs("media_player", "playlist") ``` ### 3.4 Reading data Since probably we added more than 3 songs into our database, let’s list it into our terminal. ```ex def read_data(keyspace, table) do cluster = start_link() query = "SELECT id, title, album, artist, created_at FROM #{keyspace}.#{table};" run_query(cluster, query) |> Enum.each(fn %{ "id" => id, "title" => title, "album" => album, "artist" => artist, "created_at" => created_at } -> IO.puts( "ID: #{id} | Title: #{title} | Album: #{album} | Artist: #{artist} | Created At: #{created_at}" ) end) end ``` To test just run the interactive shell again with `iex -S mix` and run: ```ex iex(1)> MediaPlayer.read_data("media_player", "playlist") ``` The result will look like: ```default ID: 09679e47-0898-40fd-b114-52b27de5a21c | Title: Stolen Dance | Album: Sadnecessary | Artist: Milky Chance | Created At: 2023-09-07 22:26:56.798Z ID: 56fac772-dc54-4518-86df-2a628a2a45f6 | Title: Still Into You | Album: Paramore | Artist: Paramore | Created At: 2023-09-07 22:26:56.798Z ID: 11bbeed9-c9a8-45cc-9842-c60483b4cb67 | Title: Getaway Car | Album: Reputation | Artist: Taylor Swift | Created At: 2023-09-07 22:26:56.798Z ``` ### 3.5 Updating data Ok, almost there! Now we’re going to learn about update but here’s a disclaimer: > INSERT and UPDATES are not equals! There’s a myth in ScyllaDB/Cassandra community that it’s the same for the fact that you just need the `Partition Key` and `Clustering Key` (if you have one) and query it. If you want to read more about it, [click here.](https://docs.scylladb.com/stable/using-scylla/cdc/cdc-basic-operations.html) As we can see, the `UPDATE QUERY` takes two fields on `WHERE` (PK and CK). Check the snippet below: ```ex def update_data(keyspace, table) do cluster = start_link() query = "UPDATE #{keyspace}.#{table} SET title = ?, album = ?, artist = ? WHERE id = ? AND created_at = ?;" {:ok, date_formated, _} = DateTime.from_iso8601("2023-09-07 22:26:56.798Z") run_query(cluster, query, [ "Getaway Car UPDATED", "Reputation", "Taylor Swift", "11bbeed9-c9a8-45cc-9842-c60483b4cb67", date_formated ]) end ``` Note that we had to convert the saved date format to iso8601. To test just run the interactive shell again with `iex -S mix` and run: ```ex iex(1)> MediaPlayer.update_data("media_player", "playlist") ``` So to check if it’s been updated: ```ex iex(2)> MediaPlayer.read_data("media_player", "playlist") ``` The result will look like: ```default ID: 09679e47-0898-40fd-b114-52b27de5a21c | Title: Stolen Dance | Album: Sadnecessary | Artist: Milky Chance | Created At: 2023-09-07 22:26:56.798Z ID: 56fac772-dc54-4518-86df-2a628a2a45f6 | Title: Still Into You | Album: Paramore | Artist: Paramore | Created At: 2023-09-07 22:26:56.798Z ID: 11bbeed9-c9a8-45cc-9842-c60483b4cb67 | Title: Getaway Car UPDATED | Album: Reputation | Artist: Taylor Swift | Created At: 2023-09-07 22:26:56.798Z ``` ### 3.5 Deleting data Let’s understand what we can DELETE with this statement. There’s the normal `DELETE` statement that focus on `ROWS` and other one that delete data only from `COLUMNS` and the syntax is very similar. ```sql -- Deletes a single row DELETE FROM songs WHERE id = d754f8d5-e037-4898-af75-44587b9cc424; -- Deletes a whole column DELETE artist FROM songs WHERE id = d754f8d5-e037-4898-af75-44587b9cc424; ``` If you want to erase a specific column, you also should pass as parameter the `Clustering Key` and be very specific in which register you want to delete something. On the other hand, the “normal delete” just need the `Partition Key` to handle it. Just remember: if you use the statement “DELETE FROM keyspace.table_name” it will delete ALL the rows that you stored with that ID. ```ex def delete_data(keyspace, table) do cluster = start_link() query = "DELETE FROM #{keyspace}.#{table} WHERE id = ? AND created_at = ?;" {:ok, date_formated, _} = DateTime.from_iso8601("2023-09-07 22:26:56.798Z") run_query(cluster, query, [ "11bbeed9-c9a8-45cc-9842-c60483b4cb67", date_formated ]) end ``` To test just run the interactive shell again with `iex -S mix` and run: ```ex iex(1)> MediaPlayer.delete_data("media_player", "playlist") ``` So to check if it’s been updated: ```ex iex(2)> MediaPlayer.read_data("media_player", "playlist") ``` The result will look like: ```default ID: 09679e47-0898-40fd-b114-52b27de5a21c | Title: Stolen Dance | Album: Sadnecessary | Artist: Milky Chance | Created At: 2023-09-07 22:26:56.798Z ID: 56fac772-dc54-4518-86df-2a628a2a45f6 | Title: Still Into You | Album: Paramore | Artist: Paramore | Created At: 2023-09-07 22:26:56.798Z ``` ## Conclusion Yay! You now have the knowledge to use the basics of ScyllaDB with Elixir. If you thinks that something can be improved, please open an issue and let’s make it happen! Did you like the content? Dont forget to star the repo and follow us on socials. # build-with-golang.md # Quick Start: Golang In this tutorial we’re going to build a simple Media Player to store our songs and build playlists. ## 1. Setup the Environment ### 1.1 Downloading Golang If you don’t have Golang installed already on your machine, you can install it from the following sources: 1. [Golang](https://go.dev/) ### 1.2 Cloning the project Clone the repository and navigate to the Go example: ```sh git clone https://github.com/scylladb/scylla-cloud-getting-started.git cd scylla-cloud-getting-started/go ``` ### 1.3 Configuring credentials Copy `.env.example` to `.env` and fill in your ScyllaDB Cloud credentials: ```sh cp .env.example .env ``` Edit `.env` with your cluster details: ```sh NODES=node-0.aws-us-east-1..clusters.scylla.cloud CLUSTER_USERNAME=scylla CLUSTER_PASSWORD=your-password # Datacenter name from the ScyllaDB Cloud dashboard (Connect tab), e.g. AWS_US_EAST_1 CLUSTER_REGION=AWS_US_EAST_1 MIGRATE_PATH=./internal/database/migrations/migrate.cql ``` > You can find the node hostnames and datacenter name in the [ScyllaDB Cloud Dashboard](https://cloud.scylladb.com/clusters) under the **Connect** tab. ### 1.4 Installing dependencies and running the project ```sh go run ./cmd/... ``` ## 2. Connecting to the Cluster The connection is set up in `internal/database/connection.go`. It reads credentials from environment variables and configures a DC-aware load balancing policy for optimal performance: ```go func Connect() (*gocqlx.Session, error) { nodes := os.Getenv("NODES") username := os.Getenv("CLUSTER_USERNAME") password := os.Getenv("CLUSTER_PASSWORD") region := os.Getenv("CLUSTER_REGION") hosts := strings.Split(nodes, ",") cluster := gocql.NewCluster(hosts...) cluster.Authenticator = gocql.PasswordAuthenticator{Username: username, Password: password} cluster.PoolConfig.HostSelectionPolicy = gocql.TokenAwareHostPolicy(gocql.DCAwareRoundRobinPolicy(region)) session, err := gocqlx.WrapSession(cluster.CreateSession()) if err != nil { return nil, err } return &session, nil } ``` > If the connection is refused, check that your IP address is added to the allowed IPs in the ScyllaDB Cloud dashboard. ## 3. Handling Queries Using the `gocqlx` package you can instantiate a session and run CQL queries. ```go type Song struct { Id string Title string Artist string Album string Created_at time.Time } func (s Song) String() string { return fmt.Sprintf("Id: %s\nTitle: %s\nArtist: %s\nAlbum: %s\nCreated At: %s\n", s.Id, s.Title, s.Artist, s.Album, s.Created_at) } ``` ### 3.1 Creating a Keyspace The `keyspace` in ScyllaDB is equivalent to a database or schema. The migration file at `internal/database/migrations/migrate.cql` creates it automatically on startup: ```cql CREATE KEYSPACE IF NOT EXISTS media_player WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '3'} AND durable_writes = true; ``` ### 3.2 Creating a table A table stores the data for your app. The migration also creates the playlist table: ```sql CREATE TABLE IF NOT EXISTS media_player.playlist ( id uuid, title text, album text, artist text, created_at timestamp, PRIMARY KEY (id, created_at) ) WITH CLUSTERING ORDER BY (created_at DESC); ``` ### 3.3 Inserting data Now that we have the keyspace and a table, we can add songs to the playlist: ```go func (c *SongController) Insert(song *database.Song) error { q := c.Session.Query( `INSERT INTO media_player.playlist (id, title, artist, album, created_at) VALUES (now(), ?, ?, ?, ?)`, []string{":title", ":artist", ":album", ":created_at"}). BindMap(map[string]interface{}{ ":title": song.Title, ":artist": song.Artist, ":album": song.Album, ":created_at": time.Now(), }) if err := q.Exec(); err != nil { return fmt.Errorf("error in exec query to insert a song in playlist %w", err) } return nil } ``` Use the `!add` command in the CLI to add a song interactively. ### 3.4 Reading data List all songs stored in the playlist: ```go func (c *SongController) List() ([]database.Song, error) { songs := []database.Song{} q := c.Session.Query("SELECT * FROM media_player.playlist", nil) if err := q.SelectRelease(&songs); err != nil { return songs, fmt.Errorf("error in exec query to list playlists: %w", err) } return songs, nil } ``` Use the `!list` command in the CLI to display all songs. Each entry will look like: ```default Id: a1500b3b-5a38-11ee-97d6-4495929e9df0 Title: My Favourite Song Artist: Some Artist Album: Great Album Created At: 2023-09-23 17:42:56.541 +0000 UTC ``` ### 3.5 Deleting data Delete a row by its partition key: ```go func (c *SongController) Delete() error { songs, err := c.List() if err != nil { return err } index, err := c.selectSongToDelete(songs) if err != nil { return err } if index >= 0 && index < len(songs) { songToDelete := songs[index] q := c.Session.Query(`DELETE FROM media_player.playlist WHERE id = ?`, []string{":id"}). BindMap(map[string]interface{}{ ":id": songToDelete.Id, }) if err := q.Exec(); err != nil { return fmt.Errorf("error to exec delete query %w", err) } } return nil } ``` Use the `!delete` command in the CLI to select and delete a song interactively. ### 3.6 Stress testing The `!stress` command inserts 100,000 records concurrently to test cluster throughput: ```go func (c *StressController) Stress() error { fmt.Println("Inserting 100,000 records into the database...") start := time.Now() var wg sync.WaitGroup sem := make(chan bool, 550) for i := 0; i < 100_000; i++ { sem <- true wg.Add(1) go func() { defer func() { <-sem wg.Done() }() q := c.Session.Query( `INSERT INTO media_player.playlist (id, title, artist, album, created_at) VALUES (now(), ?, ?, ?, ?)`, []string{":title", ":artist", ":album", ":created_at"}). BindMap(map[string]interface{}{ ":title": "title teste", ":artist": "artist teste", ":album": "album teste", ":created_at": time.Now(), }) if err := q.Exec(); err != nil { fmt.Println(err.Error()) } }() } wg.Wait() fmt.Println("Time taken:", time.Since(start)) return nil } ``` ## Conclusion You now have the knowledge to use the basics of ScyllaDB with Golang. If you think something can be improved, please open an issue and let’s make it happen! Did you like the content? Don’t forget to star the repo and follow us on socials. # build-with-java.md # Quick start: Java In this tutorial you’ll build a Media Player to store your songs and build playlists. ## 1. Getting the Driver Add the [ScyllaDB Java Driver](https://java-driver.docs.scylladb.com/) to your `pom.xml`: ```xml com.scylladb java-driver-core 4.18.0.0 ``` To package your application as a fat JAR (all dependencies included), use the `maven-shade-plugin`: ```xml org.apache.maven.plugins maven-shade-plugin 3.5.1 package shade ${project.build.directory}/app.jar com.scylladb.App reference.conf ``` ## 2. Connecting to the cluster Get your database credentials from your [ScyllaDB Cloud Cluster View](https://cloud.scylladb.com/clusters) in the tab `Connect`. > Add your machine’s IP Address to the list of allowed IP addresses in ScyllaDB Cloud. Otherwise, your connection will get refused. ```java import com.datastax.oss.driver.api.core.CqlSession; import java.net.InetSocketAddress; public class App { public static void main(String[] args) { CqlSession session = CqlSession.builder() .addContactPoint(new InetSocketAddress("node-0.aws-us-east-1.xxxx.clusters.scylla.cloud", 9042)) .withAuthCredentials("scylla", "your-awesome-password") .withLocalDatacenter("AWS_US_EAST_1") .build(); } } ``` ## 3. Handling Queries With the ScyllaDB Java driver 4.x, use `session.execute(query)` to run CQL statements. ```java import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import java.net.InetSocketAddress; public class App { public static void main(String[] args) { CqlSession session = CqlSession.builder() .addContactPoint(new InetSocketAddress("node-0.aws-us-east-1.xxxx.clusters.scylla.cloud", 9042)) .withAuthCredentials("scylla", "your-awesome-password") .withLocalDatacenter("AWS_US_EAST_1") .build(); ResultSet result = session.execute("SELECT * FROM system.clients LIMIT 10"); for (Row row : result) { System.out.println(row.toString()); } session.close(); } } ``` ### 3.1 Create a keyspace A keyspace in ScyllaDB is a collection of tables with attributes which define how data is replicated on nodes. ```java import com.datastax.oss.driver.api.core.CqlSession; import java.net.InetSocketAddress; public class App { public static void main(String[] args) { CqlSession session = CqlSession.builder() .addContactPoint(new InetSocketAddress("node-0.aws-us-east-1.xxxx.clusters.scylla.cloud", 9042)) .withAuthCredentials("scylla", "your-awesome-password") .withLocalDatacenter("AWS_US_EAST_1") .build(); session.execute( "CREATE KEYSPACE IF NOT EXISTS media_player " + "WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '3'} " + "AND durable_writes = true" ); session.close(); } } ``` ### 3.2 Create table A table stores part or all of your app data (depending on how you structure your database schema). Add the `keyspace` prefix in your table name and define a CQL string that creates a table to store your favorite songs. ```java import com.datastax.oss.driver.api.core.CqlSession; import java.net.InetSocketAddress; public class App { public static void main(String[] args) { CqlSession session = CqlSession.builder() .addContactPoint(new InetSocketAddress("node-0.aws-us-east-1.xxxx.clusters.scylla.cloud", 9042)) .withAuthCredentials("scylla", "your-awesome-password") .withLocalDatacenter("AWS_US_EAST_1") .build(); session.execute( "CREATE TABLE IF NOT EXISTS media_player.playlist (" + " id uuid," + " title text," + " album text," + " artist text," + " created_at timestamp," + " PRIMARY KEY (id, created_at)" + ") WITH CLUSTERING ORDER BY (created_at DESC)" ); session.close(); } } ``` ### 3.3 Insert data Now that you have created a keyspace and a table, insert some songs to populate the table. ```java import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import java.net.InetSocketAddress; import java.time.Instant; import java.util.UUID; public class App { public static void main(String[] args) { CqlSession session = CqlSession.builder() .addContactPoint(new InetSocketAddress("node-0.aws-us-east-1.xxxx.clusters.scylla.cloud", 9042)) .withAuthCredentials("scylla", "your-awesome-password") .withLocalDatacenter("AWS_US_EAST_1") .build(); PreparedStatement ps = session.prepare( "INSERT INTO media_player.playlist (id, title, artist, album, created_at) VALUES (?, ?, ?, ?, ?)" ); session.execute(ps.bind( UUID.randomUUID(), "Stairway to Heaven", "Led Zeppelin", "Led Zeppelin IV", Instant.now() )); session.close(); } } ``` ### 3.4 Read data Let’s read the songs from the database and print them to the terminal. ```java import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.ResultSet; import com.datastax.oss.driver.api.core.cql.Row; import java.net.InetSocketAddress; public class App { public static void main(String[] args) { CqlSession session = CqlSession.builder() .addContactPoint(new InetSocketAddress("node-0.aws-us-east-1.xxxx.clusters.scylla.cloud", 9042)) .withAuthCredentials("scylla", "your-awesome-password") .withLocalDatacenter("AWS_US_EAST_1") .build(); ResultSet rs = session.execute( "SELECT id, title, album, artist, created_at FROM media_player.playlist PER PARTITION LIMIT 1 LIMIT 100" ); for (Row row : rs) { System.out.printf( "ID: %s -> Song: %s -> Artist: %s -> Album: %s -> Created At: %s%n", row.getUuid("id"), row.getString("title"), row.getString("artist"), row.getString("album"), row.getInstant("created_at") ); } session.close(); } } ``` ### 3.5 Update data The `UPDATE` query takes two fields in the `WHERE` clause (`partition key` and `clustering key`). See the snippet below: ```java import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import java.net.InetSocketAddress; import java.time.Instant; import java.util.UUID; public class App { public static void main(String[] args) { CqlSession session = CqlSession.builder() .addContactPoint(new InetSocketAddress("node-0.aws-us-east-1.xxxx.clusters.scylla.cloud", 9042)) .withAuthCredentials("scylla", "your-awesome-password") .withLocalDatacenter("AWS_US_EAST_1") .build(); UUID songId = UUID.fromString("d754f8d5-e037-4898-af75-44587b9cc424"); PreparedStatement ps = session.prepare( "UPDATE media_player.song_counter SET times_played = times_played + 1 WHERE song_id = ?" ); session.execute(ps.bind(songId)); session.close(); } } ``` After the data gets inserted, query all columns and filter by the ID: ```default scylla@cqlsh:media_player> select * from playlist where id = d754f8d5-e037-4898-af75-44587b9cc424; id | created_at | album | artist | title --------------------------------------+---------------------------------+-----------------+-------------+------------------ d754f8d5-e037-4898-af75-44587b9cc424 | 2023-03-02 22:00:00.000000+0000 | Led Zeppelin IV | Led Zeppelin | Stairway to Heaven ``` ### 3.6 Delete Data Last things last! Let’s understand what we can DELETE with this statement. There’s the regular `DELETE` statement that focuses on `ROWS` and the other one that deletes data only from `COLUMNS`. The syntax is very similar. ```sql -- Deletes all rows for a partition DELETE FROM media_player.playlist WHERE id = d754f8d5-e037-4898-af75-44587b9cc424; -- Deletes a specific cell DELETE artist FROM media_player.playlist WHERE id = d754f8d5-e037-4898-af75-44587b9cc424 AND created_at = '2023-03-02 22:00:00+0000'; ``` If you want to remove a specific column, you also should pass the `Clustering Key` as parameter and be specific about which row you want to delete something from. On the other hand, deleting by partition key only will delete ALL the rows stored with that ID. ```java import com.datastax.oss.driver.api.core.CqlSession; import com.datastax.oss.driver.api.core.cql.PreparedStatement; import java.net.InetSocketAddress; import java.util.UUID; public class App { public static void main(String[] args) { CqlSession session = CqlSession.builder() .addContactPoint(new InetSocketAddress("node-0.aws-us-east-1.xxxx.clusters.scylla.cloud", 9042)) .withAuthCredentials("scylla", "your-awesome-password") .withLocalDatacenter("AWS_US_EAST_1") .build(); UUID songId = UUID.fromString("d754f8d5-e037-4898-af75-44587b9cc424"); PreparedStatement ps = session.prepare( "DELETE FROM media_player.playlist WHERE id = ?" ); session.execute(ps.bind(songId)); session.close(); } } ``` ## Conclusion Yay! You now know how to get started with ScyllaDB in Java. There is a simple project with this structure that you can check out [here](https://github.com/scylladb/scylla-cloud-getting-started/tree/main/java). If you think something can be improved, please open an issue and let’s make it happen! Did you like the content? Don’t forget to star the repo and follow us on socials. # build-with-javascript.md # Quick start: JavaScript (Node.js) In this tutorial you’ll build a Media Player to store your songs and build playlists. ## 1. Getting the Driver Install the [ScyllaDB Node.js RS Driver](https://github.com/scylladb/nodejs-rs-driver), a native ScyllaDB driver built on top of the ScyllaDB Rust driver. ```sh $ npm install @scylladb/driver $ yarn add @scylladb/driver ``` ## 2. Connect to the cluster Get your database credentials from your [ScyllaDB Cloud Dashboard](https://cloud.scylladb.com/clusters) in the tab `Connect`. > Add your machine’s IP Address to the list of allowed IP addresses in ScyllaDB Cloud. Otherwise, your connection will get refused. ```js const cluster = new cassandra.Client({ contactPoints: ["your-node-url.clusters.scylla.cloud", "your-node-url.clusters.scylla.cloud", ...], localDataCenter: 'your-data-center', // Eg: AWS_SA_EAST_1 credentials: {username: 'scylla', password: 'your-awesome-password'}, // keyspace: 'your_keyspace' // optional }) ``` ## 3. Handling Queries With the NodeJS driver, you can use the function inside your cluster connection called `execute(query)` and build the query you want to execute inside your database/keyspace. ```js const cluster = new cassandra.Client({ contactPoints: ["your-node-url.clusters.scylla.cloud", "your-node-url.clusters.scylla.cloud", ...], localDataCenter: 'your-data-center', // Eg: AWS_SA_EAST_1 credentials: {username: 'scylla', password: 'your-awesome-password'} }) const results = await cluster.execute('SELECT * FROM system.clients LIMIT 10') console.log(results); results.rows.forEach(row => console.log(JSON.stringify(row))) ``` ### 3.1 Create a keyspace The `keyspace` inside the ScyllaDB ecosystem can be interpreted as your `database` or `collection`. On your connection boot, you don’t need to provide it but you will use it later and also is able to create when you need. ```js async function runKeyspace () { const cluster = new cassandra.Client({ contactPoints: ["your-node-url.clusters.scylla.cloud", "your-node-url.clusters.scylla.cloud", ...], localDataCenter: 'your-data-center', // Eg: AWS_SA_EAST_1 credentials: {username: 'scylla', password: 'your-awesome-password'}, }) const newKeyspace = (keyspaceName) => `CREATE KEYSPACE IF NOT EXISTS ${keyspaceName} WITH replication = {'class': 'NetworkTopologyStrategy', 'replication_factor': '3'};`; await cluster.execute(newKeyspace('media_player')) await cluster.shutdown() } runKeyspace(); ``` > After that you probably will need to re-create your connection poiting which `keyspace` you want to use. ### 3.2 Creating a Table A table is used to store part or all of your app data (depending on how structure your database schema). Add the `keyspace` as a parameter in the connection object and define a CQL string that creates a table to store your favorite songs. ```js async function runKeyspace (keyspace = null) { const cluster = new cassandra.Client({ contactPoints: ["your-node-url.clusters.scylla.cloud", "your-node-url.clusters.scylla.cloud", ...], localDataCenter: 'your-data-center', // Eg: AWS_SA_EAST_1 credentials: {username: 'scylla', password: 'your-awesome-password'}, keyspace: keyspace ?? 'media_player' }) const createSongsTableQuery = ` CREATE TABLE IF NOT EXISTS songs ( id uuid, title text, album text, artist text, created_at timestamp, updated_at timestamp, PRIMARY KEY (id, updated_at) ) `; await cluster.execute(createSongsTableQuery) await cluster.shutdown() } runKeyspace('media_player'); ``` ### 3.3 Inserting data Now that you have created a keyspace and a table, you need to insert some songs to populate the table. ```js async function insertSongs (keyspace = 'media_player') { const cluster = new cassandra.Client({ contactPoints: ["your-node-url.clusters.scylla.cloud", "your-node-url.clusters.scylla.cloud", ...], localDataCenter: 'your-data-center', // Eg: AWS_SA_EAST_1 credentials: {username: 'scylla', password: 'your-awesome-password'}, keyspace: keyspace }) let songList = [ { id: cassandra.types.Uuid.random(), title: 'Stairway to Heaven', album: 'Led Zeppelin IV', artist: 'Led Zeppelin', createdAt: '2023-03-02 22:00:00', updatedAt: '2023-03-02 22:00:00', }, { id: 'd754f8d5-e037-4898-af75-44587b9cc424', title: 'Glimpse of Us', album: 'Smithereens', artist: 'Joji', createdAt: '2023-03-02 22:00:00', updatedAt: '2023-03-02 22:00:00', }, { id: cassandra.types.Uuid.random(), title: 'Vegas', album: 'From Movie ELVIS', artist: 'Doja Cat', createdAt: '2023-03-02 22:00:00', updatedAt: '2023-03-02 22:00:00', }, ]; const newSongQuery = (song) => { return `INSERT INTO songs (id, title, album, artist, created_at, updated_at) VALUES (${song.id}, '${song.title}', '${song.album}', '${song.artist}', '${song.createdAt}', '${song.updatedAt}')` } for (let i in songList) { await cluster.execute(newSongQuery(songList[i])) } await cluster.shutdown() } ``` ### 3.3 Reading data Since probably we added more than 3 songs into our database, let’s list it into our terminal. ```js const listSongs = async (keyspace = 'media_player') => { const cluster = new cassandra.Client({ contactPoints: ["your-node-url.clusters.scylla.cloud", "your-node-url.clusters.scylla.cloud", ...], localDataCenter: 'your-data-center', // Eg: AWS_SA_EAST_1 credentials: {username: 'scylla', password: 'your-awesome-password'}, keyspace: keyspace }) let results = await cluster.execute("SELECT * FROM songs"); let rows = results.rows; for (let i in rows) { console.log(rows[i]) console.log(rows[i].id.toString()) } await cluster.shutdown() } ``` The result looks like this: ```default $ node index.js Row { id: Uuid { buffer: }, title: 'Glimpse of Us', album: 'Smithereens', artist: 'Joji' } ... ``` > Remeber to decode your Uuid if needed using the function `.toString()` ### 3.4 Updating Data Ok, almost there! Now we’re going to learn about `UPDATE` but here’s a disclaimer: > INSERT and UPDATES are not the same! There’s a myth in ScyllaDB/Cassandra community that it’s the same for the fact that you just need the `Partition Key` and `Clustering Key` (if you have one) and query it. Read more about [`INSERT` and `UPDATE`](https://docs.scylladb.com/stable/using-scylla/cdc/cdc-basic-operations.html) As you can see, the `UPDATE` query takes two fields in the `WHERE` clause (PK and CK). Check the snippet below: ```js const songToUpdate = { id: 'd754f8d5-e037-4898-af75-44587b9cc424', title: 'Glimpse of Us', album: 'Smithereens', artist: 'Joji', }; const updateSong = async (song) => { const cluster = new cassandra.Client({ contactPoints: ["your-node-url.clusters.scylla.cloud", "your-node-url.clusters.scylla.cloud", ...], localDataCenter: 'your-data-center', // Eg: AWS_SA_EAST_1 credentials: {username: 'scylla', password: 'your-awesome-password'}, keyspace: 'media_player' }) let results = await cluster.execute("SELECT * FROM songs"); let rows = results.rows; let songToUpdate = rows.find((row) => row.id.toString() === song.id) await cluster.execute(`UPDATE songs set title = 'Glimpse of US - Inutilismo' where id = ${songToUpdate.id} AND updated_at = '2023-03-02 23:10:00.00+0000';`); await cluster.shutdown() } ``` After the data gets inserted, query all columns and filter by the ID: ```default scylla@cqlsh:media_player> select * from songs where id = d754f8d5-e037-4898-af75-44587b9cc424; id | updated_at | album | artist | created_at | title --------------------------------------+---------------------------------+-------------+--------+---------------------------------+---------------------------- d754f8d5-e037-4898-af75-44587b9cc424 | 2023-03-02 22:00:00.000000+0000 | Smithereens | Joji | 2023-03-02 22:00:00.000000+0000 | Glimpse of Us d754f8d5-e037-4898-af75-44587b9cc424 | 2023-03-02 23:10:00.000000+0000 | null | null | null | Glimpse of US - Inutilismo ``` It only updated the field `title` and `updated_at` (the Clustering Key) and since we didn’t input the rest of the data, it will not be replicated as expected. ### 3.5 Deleting Data Last things last! Let’s understand what we can DELETE with this statement. There’s the normal `DELETE` statement that focus on `ROWS` and other one that delete data only from `COLUMNS` and the syntax is very similar. ```sql // Deletes a single row DELETE FROM songs WHERE id = d754f8d5-e037-4898-af75-44587b9cc424; // Deletes a whole column DELETE artist FROM songs WHERE id = d754f8d5-e037-4898-af75-44587b9cc424; ``` If you want to erase a specific column, you also should pass as parameter the `Clustering Key` and be very specific in which register you want to delete something. On the other hand, the “normal delete” just need the `Partition Key` to handle it. Just remember: if you use the statement `DELETE FROM ` it will delete ALL the rows that you stored with that ID. ```js const deleteColumnFromSong = async (song) => { const cluster = new cassandra.Client({ contactPoints: ["your-node-url.clusters.scylla.cloud", "your-node-url.clusters.scylla.cloud", ...], localDataCenter: 'your-data-center', // Eg: AWS_SA_EAST_1 credentials: {username: 'scylla', password: 'your-awesome-password'}, keyspace: 'media_player' }) await cluster.execute(`DELETE artist FROM songs WHERE id = ${song.id} AND updated_at = '${song.updatedAt}'`) await cluster.shutdown() } const deleteSong = async (song) => { const cluster = new cassandra.Client({ contactPoints: ["your-node-url.clusters.scylla.cloud", "your-node-url.clusters.scylla.cloud", ...], localDataCenter: 'your-data-center', // Eg: AWS_SA_EAST_1 credentials: {username: 'scylla', password: 'your-awesome-password'}, keyspace: 'media_player' }) await cluster.execute(`DELETE FROM songs WHERE id = ${song.id}`) await cluster.shutdown() } ``` ## Conclusion Yay! You now know how get started with ScyllaDB in Node.js. If you think something can be improved, please open an issue and let’s make it happen! Did you like the content? Don’t forget to star the repo and follow us on socials. # build-with-php.md # ScyllaDB Alternator: PHP Example Using the “PHP AWS SDK” we can easily connect our project on any ScyllaDB instance (Local/Cloud) that is configured with Alternator flag. ## Quick Start Create a new folder If you’re starting a new project, make sure to require the SDK package in your application. ```shell composer require aws/aws-sdk-php ``` You can instantiate a new DynamoDB Client by using: ```php use Aws\DynamoDb\DynamoDbClient; $alternatorClient = new DynamoDbClient([ 'endpoint' => 'http://localhost:8000', // ScyllaDB Alternator LocalHost URL 'credentials' => ['key' => 'None', 'secret' => 'None'], 'region' => 'None' ]); ``` ## Queries Here’s the DynamoDB queries used on this project so far: ### Creating a Table ```php use Aws\DynamoDb\DynamoDbClient; $alternatorClient = new DynamoDbClient([ 'endpoint' => 'http://localhost:8000', // ScyllaDB Alternator LocalHost URL 'credentials' => ['key' => 'None', 'secret' => 'None'], 'region' => 'None' ]); $alternatorClient->createTable(['TableName' => 'songs', 'KeySchema' => [ ['AttributeName' => 'id', 'KeyType' => 'HASH'], ['AttributeName' => 'created_at', 'KeyType' => 'RANGE'], ], 'AttributeDefinitions' => [ ['AttributeName' => 'id', 'AttributeType' => 'S'], ['AttributeName' => 'created_at', 'AttributeType' => 'S'], ['AttributeName' => 'title', 'AttributeType' => 'S'], ['AttributeName' => 'artist', 'AttributeType' => 'S'], ['AttributeName' => 'album', 'AttributeType' => 'S'], ], 'ProvisionedThroughput' => [ 'ReadCapacityUnits' => 10, 'WriteCapacityUnits' => 10 ] ]); ``` ### Adding Items ```php use Aws\DynamoDb\DynamoDbClient; $alternatorClient = new DynamoDbClient([ 'endpoint' => 'http://localhost:8000', // ScyllaDB Alternator LocalHost URL 'credentials' => ['key' => 'None', 'secret' => 'None'], 'region' => 'None' ]); $alternatorClient->putItem('PutRequest' => [ 'TableName' => 'songs', 'Item' => [ 'id' => ['S' => 'string'], 'created_at' => ['S' => 'string'], 'title' => ['S' => 'string'], 'album' => ['S' => 'string'], 'artist' => ['S' => 'string'], ] ]); ``` ### Listing Items ```php use Aws\DynamoDb\DynamoDbClient; use Aws\DynamoDb\Marshaler; $marshaler = new Marshaler(); $alternatorClient = new DynamoDbClient([ 'endpoint' => 'http://localhost:8000', // ScyllaDB Alternator LocalHost URL 'credentials' => ['key' => 'None', 'secret' => 'None'], 'region' => 'None' ]); $results = $client->scan(['TableName' => 'songs']); foreach ($results['Items'] as $item) { $parsedItem = $marshaler->unmarshalItem($item); var_dump($parsedItem['title']) } ``` ### Deleting Items ```php use Aws\DynamoDb\DynamoDbClient; use Aws\DynamoDb\Marshaler; $marshaler = new Marshaler(); $alternatorClient = new DynamoDbClient([ 'endpoint' => 'http://localhost:8000', // ScyllaDB Alternator LocalHost URL 'credentials' => ['key' => 'None', 'secret' => 'None'], 'region' => 'None' ]); $client->deleteItem([ 'TableName' => 'songs', 'Key' => [ 'id' => ['S' => 'some-uuid-here'], 'created_at' => ['S' => 'Y-m-d H:i:s'], ], ]); ``` # build-with-python.md # Quick start: Python In this tutorial you’ll build a Media Player app to store your songs and create playlists. ## 1. Get the development environment ready ### 1.1 Prerequisites: * [Python 3.7+](https://www.python.org/downloads/) * [Virtualenv](https://virtualenv.pypa.io/en/latest/how-to/install.html) ### 2.2 Set up the environment Create a new folder for the project: ```sh mkdir scylladb-cloud-python cd scylladb-cloud-python ``` Create a new virtual environment and activate it: ```bash virtualenv env source env/bin/activate ``` Install the [Python ScyllaDB Driver](https://pypi.org/project/scylla-driver/). ```bash pip install scylla-driver ``` ## 2. Connect to the Cluster Get your database credentials from your [ScyllaDB Cloud Dashboard](https://cloud.scylladb.com/clusters) in the tab `Connect`. > Add your machine’s IP Address to the list of allowed IP addresses in ScyllaDB Cloud. Otherwise, your connection will get refused. ```python from cassandra.cluster import Cluster from cassandra.auth import PlainTextAuthProvider cluster = Cluster( contact_points=[ "your-node-url-1.clusters.scylla.cloud", "your-node-url-2.clusters.scylla.cloud", "your-node-url-3.clusters.scylla.cloud", ], auth_provider=PlainTextAuthProvider(username='scylla', password='your-awesome-password') ) ``` ## 3. Handling Queries With the Python driver, you can use the function inside your cluster connection called `execute(query)` and build the query you want to execute inside your database/keyspace. You also can use the `execute_async()` for asynchronous queries. ```python from cassandra.cluster import Cluster from cassandra.auth import PlainTextAuthProvider cluster = Cluster( contact_points=[ "your-node-url-1.clusters.scylla.cloud", "your-node-url-2.clusters.scylla.cloud", "your-node-url-3.clusters.scylla.cloud", ], auth_provider=PlainTextAuthProvider(username='scylla', password='your-awesome-password') ) session = cluster.connect() results = session.execute('SELECT * FROM system.clients LIMIT 10') for item in results: print(item) print(item.address) ``` Response will be a ResultSet of Rows: ```default Row(address='123.123.123.123', port=46160, client_type='cql', connection_stage='AUTHENTICATING', driver_name='Scylla Python Driver', driver_version='3.26.2', hostname=None, protocol_version=4, shard_id=0, ssl_cipher_suite=None, ssl_enabled=None, ssl_protocol=None, username='scylla') 123.123.123.123 ``` ### 3.1 Create a Keyspace The `keyspace` inside the ScyllaDB ecosystem can be interpreted as your `database` or `collection`. On your connection boot, you don’t need to provide it but you will use it later and also is able to create when you need. {% raw %} ```python from cassandra.cluster import Cluster from datetime import datetime from cassandra.auth import PlainTextAuthProvider cluster = Cluster( contact_points=[ "your-node-url-1.clusters.scylla.cloud", "your-node-url-2.clusters.scylla.cloud", "your-node-url-3.clusters.scylla.cloud", ], auth_provider=PlainTextAuthProvider(username='scylla', password='your-awesome-password') ) session = cluster.connect() keyspaceName = "media_player" session.execute("CREATE KEYSPACE {};".format(keyspaceName)) session.set_keyspace('media_player') ``` {% endraw %} Unfortunately you can’t set a keyspace with `PreparedStatements`, so you will need to build the query by yourself. > You can use the `session.set_keyspace()` function to switch between keyspaces. ### 3.2 Creating a Table A table is used to store a part or all of your app data (depending on how you structure your database schema). Add the `keyspace` as a parameter in the connection object and define a CQL string that creates a table to store your favorite songs. ```python from cassandra.cluster import Cluster from datetime import datetime from cassandra.auth import PlainTextAuthProvider cluster = Cluster( contact_points=[ "your-node-url-1.clusters.scylla.cloud", "your-node-url-2.clusters.scylla.cloud", "your-node-url-3.clusters.scylla.cloud", ], auth_provider=PlainTextAuthProvider(username='scylla', password='your-awesome-password') ) session = cluster.connect('media_player') tableQuery = """ CREATE TABLE songs ( id uuid, title text, album text, artist text, created_at timestamp, PRIMARY KEY (id, created_at) ) """ session.execute(tableQuery) ``` ### 3.3 Insert data Now that you have created a keyspace and a table, insert some songs to populate the table. ```python from cassandra.cluster import Cluster from datetime import datetime from cassandra.auth import PlainTextAuthProvider import uuid cluster = Cluster( contact_points=[ "your-node-url-1.clusters.scylla.cloud", "your-node-url-2.clusters.scylla.cloud", "your-node-url-3.clusters.scylla.cloud", ], auth_provider=PlainTextAuthProvider(username='scylla', password='your-awesome-password') ) session = cluster.connect('media_player') songList = [ { "id": uuid.UUID('d754f8d5-e037-4898-af75-44587b9cc424'), "title": 'Glimpse of Us', "album": 'Smithereens', "artist": 'Joji', "createdAt": datetime.now(), }, { "id": uuid.uuid4(), "title": 'Stairway to Heaven', "album": 'Led Zeppelin III', "artist": 'Led Zeppelin', "createdAt": datetime.now(), }, { "id": uuid.uuid4(), "title": 'Vegas', "album": 'From Movie ELVIS', "artist": 'Doja Cat', "createdAt": datetime.now(), }, ]; query = session.prepare(""" INSERT INTO songs (id, title, album, artist, created_at) VALUES (?, ?, ?, ?, ?) """) for music in songList: session.execute(query, music.values()) ``` ### 3.3 Read data Since probably we added more than 3 songs into our database, let’s list it into our terminal. ```python from cassandra.cluster import Cluster from datetime import datetime from cassandra.auth import PlainTextAuthProvider cluster = Cluster( contact_points=[ "your-node-url-1.clusters.scylla.cloud", "your-node-url-2.clusters.scylla.cloud", "your-node-url-3.clusters.scylla.cloud", ], auth_provider=PlainTextAuthProvider(username='scylla', password='your-awesome-password') ) session = cluster.connect('media_player') query = session.prepare("SELECT * FROM songs"); results = session.execute(query); for song in results: print(song.id) print(song.title) ``` The result looks like this: ```default 0a5d5cfb-0275-43a4-9523-45ad256666f8 Vegas ``` ### 3.4 Update data The `UPDATE` query in the fact is equals to `INSERT` regarding the syntax. You just need the `Partition Key` and `Clustering Key` (if you have one) and query it. The `UPDATE` query takes two fields in the `WHERE` clause (PK and CK). See the snippet below: ```py from cassandra.cluster import Cluster from datetime import datetime from cassandra.auth import PlainTextAuthProvider import uuid cluster = Cluster( contact_points=[ "your-node-url-1.clusters.scylla.cloud", "your-node-url-2.clusters.scylla.cloud", "your-node-url-3.clusters.scylla.cloud", ], auth_provider=PlainTextAuthProvider(username='scylla', password='your-awesome-password') ) session = cluster.connect('media_player') songToUpdate = { "id": uuid.UUID('d754f8d5-e037-4898-af75-44587b9cc424'), "title": 'Glimpse of Us', "album": '2022 Em Uma Música', "artist": 'Lucas Inutilismo', "createdAt": datetime.now() } session.execute(""" UPDATE songs SET title = %(title)s, album = %(album)s, artist = %(artist)s WHERE id = %(id)s AND created_at = %(createdAt)s """, songToUpdate) ``` After the data gets inserted, query all columns and filter by the ID: ```default scylla@cqlsh:media_player> select * from songs where id = d754f8d5-e037-4898-af75-44587b9cc424; id | created_at | album | artist | title --------------------------------------+---------------------------------+--------------------+------------------+--------------- d754f8d5-e037-4898-af75-44587b9cc424 | 2023-07-11 14:22:43.329000+0000 | Smithereens | Joji | Glimpse of Us d754f8d5-e037-4898-af75-44587b9cc424 | 2023-07-11 14:23:31.630000+0000 | 2022 Em Uma Música | Lucas Inutilismo | Glimpse of Us d754f8d5-e037-4898-af75-44587b9cc424 | 2023-07-11 14:23:51.996000+0000 | 2022 Em Uma Música | null | Glimpse of Us (3 rows) ``` In the snippet above, we updated the data fully but as you can see in the `SELECT` query, I ran one more update but without the `artist` column. So, as said before: the only “not nullable” fields are the `Partition Keys` and `Clustering Keys`. ### 3.5 Delete Data Let’s understand what we can DELETE with this statement. There’s the normal `DELETE` statement that focuses on `ROWS` and another one that deletes data only from `COLUMNS` and the syntax is very similar. ```sql // Deletes a single row DELETE FROM songs WHERE id = d754f8d5-e037-4898-af75-44587b9cc424; // Deletes a whole column DELETE artist FROM songs WHERE id = d754f8d5-e037-4898-af75-44587b9cc424; ``` If you want to remove a specific column, you also should pass as parameter the `Clustering Key` and be very specific in which register you want to delete something. On the other hand, the “normal delete” just need the `Partition Key` to handle it. Just remember: if you use the statement “DELETE FROM keyspace.table_name” it will delete ALL the rows that you stored with that ID. ```py from cassandra.cluster import Cluster from datetime import datetime from cassandra.auth import PlainTextAuthProvider import uuid songToDelete = { "id": uuid.UUID('d754f8d5-e037-4898-af75-44587b9cc424'), "title": 'Glimpse of Us', "album": '2022 Em Uma Música', "artist": 'Lucas Inutilismo', "createdAt": datetime.now() } cluster = Cluster( contact_points=[ "your-node-url-1.clusters.scylla.cloud", "your-node-url-2.clusters.scylla.cloud", "your-node-url-3.clusters.scylla.cloud", ], auth_provider=PlainTextAuthProvider(username='scylla', password='your-awesome-password') ) session = cluster.connect('media_player') deleteQuery = session.prepare("DELETE FROM songs WHERE id = ? ") session.execute(deleteQuery, [songToDelete['id']]) ``` ## 4. Conclusion Yay! You now know how get started with ScyllaDB in Python. If you think something can be improved, please open an issue and let’s make it happen! There is a sample project that you can learn more about the concepts and also have a good time testing our ScyllaDB Cloud Cluster, check it out [here](https://github.com/scylladb/scylla-cloud-getting-started/tree/main/python). Did you like the content? Don’t forget to star the repo and follow us on socials. # build-with-python.md # ScyllaDB Alternator: Python Example Using the “Python AWS SDK” we can easily connect our project on any ScyllaDB instance (Local/Cloud) that is configured with Alternator flag. ## Quick Start Create a new folder If you’re starting a new project, make sure to require the SDK package in your application. ```shell sudo pip install --upgrade boto3 ``` You can instantiate a new DynamoDB Client by using: ```python import boto3 alternator = boto3.resource('dynamodb',endpoint_url='http://localhost:8000', region_name='None', aws_access_key_id='None', aws_secret_access_key='None') ``` ## Queries Here’s the DynamoDB queries used on this project so far: ### Creating a Table ```python import boto3 alternator = boto3.resource('dynamodb',endpoint_url='http://localhost:8000', region_name='None', aws_access_key_id='None', aws_secret_access_key='None') table = alternator.create_table( TableName="songs", KeySchema=[ {'AttributeName': 'id', 'KeyType': 'HASH'}, {'AttributeName': 'created_at', 'KeyType': 'RANGE'}, ], AttributeDefinitions=[ {'AttributeName': 'id', 'AttributeType': 'S'}, {'AttributeName': 'created_at', 'AttributeType': 'S'}, {'AttributeName': 'title', 'AttributeType': 'S'}, {'AttributeName': 'artist', 'AttributeType': 'S'}, {'AttributeName': 'album', 'AttributeType': 'S'}, ], ProvisionedThroughput={'ReadCapacityUnits': 10, 'WriteCapacityUnits': 10} ); table.wait_until_exists() table.put_item(Item={ 'id': str(uuid.uuid4()), 'created_at': str(datetime.now()), 'title': 'Song 1', 'album': 'Album 1', 'artist': 'artistName', }) ``` ### Adding Items ```python alternator = boto3.resource('dynamodb',endpoint_url='http://localhost:8000', region_name='None', aws_access_key_id='None', aws_secret_access_key='None') table = alternator.Table('songs') table.put_item(Item={ 'id': str(uuid.uuid4()), 'created_at': str(datetime.now()), 'title': 'Song 1', 'album': 'Album 1', 'artist': 'artistName', }) ``` ### Listing Items ```python alternator = boto3.resource('dynamodb',endpoint_url='http://localhost:8000', region_name='None', aws_access_key_id='None', aws_secret_access_key='None') table = alternator.Table('songs') listSongs = table.scan() enumSongList = list(enumerate(listSongs['Items'])) for rowIndex, song in enumSongList: print(f"Index: {rowIndex} | Title: {song['title']} | Album: {song['album']} ") ``` ### Delete Item ```python alternator = boto3.resource('dynamodb',endpoint_url='http://localhost:8000', region_name='None', aws_access_key_id='None', aws_secret_access_key='None') table = alternator.Table('songs') listSongs = table.scan() table.delete_item(Key={ 'id': 'id', 'created_at': 'y-m-d H:i:s' }) ``` # build-with-rust.md # Quick start: Rust (Crablang) In this tutorial you’ll build a simple Media Player to store your songs and manage a playlist via the command line. ## 1. Setup the Environment Let’s download Rust and the dependencies needed for this project. ### 1.1 Downloading Rust and Dependencies If you don’t have Rust installed yet, run the command below — it installs Rust and Cargo. ```default $ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` ### 1.2 Starting the project Now with Rust and Cargo installed, create a new project: ```sh cargo new media_player cd media_player ``` ### 1.3 Setting the project dependencies Update `Cargo.toml` with the project dependencies: ```toml [package] name = "media_player" version = "0.1.0" edition = "2021" [dependencies] anyhow = "1.0.72" chrono = "0.4.26" clap = { version = "4.3.21", features = ["derive"] } futures = "0.3.28" rand = "0.8.5" scylla = { version = "1.0", features = ["chrono-04"] } serde = { version = "1.0.174", features = ["derive", "serde_derive"] } [dependencies.tokio] version = "1" features = ["full"] [dependencies.uuid] version = "1.4.1" features = [ "v4", "fast-rng", "macro-diagnostics", ] ``` * [Scylla](https://crates.io/crates/scylla): ScyllaDB Rust driver. `chrono-04` enables serialization of `chrono` types. * [Clap](https://crates.io/crates/clap): CLI argument parsing. * [Uuid](https://crates.io/crates/uuid): UUID generation. * [Tokio](https://crates.io/crates/tokio): Async runtime for Rust. * [Anyhow](https://crates.io/crates/anyhow): Idiomatic error handling. * [Chrono](https://crates.io/crates/chrono): DateTime/Timestamp handling. * [Futures](https://crates.io/crates/futures): Utilities for working with Rust futures. ## 2. Connecting to the Cluster Make sure to get the right credentials on your [ScyllaDB Cloud Dashboard](https://cloud.scylladb.com/clusters) in the tab `Connect`. The app accepts connection details as CLI arguments. Define the argument struct with [clap](https://crates.io/crates/clap): ```rust use clap::Parser; #[derive(Parser, Default, Debug)] #[clap(author, version, about)] pub struct ConnectionDetails { /// Scylla Cloud Node URL's (one or more, space- or comma-separated) #[arg(num_args = 1.., value_parser, value_delimiter = ',')] pub nodes: Vec, /// Cluster Username #[arg(short, long)] username: String, /// Cluster Password #[arg(short, long)] password: String, } ``` Then build the session in `database.rs`: ```rust use anyhow::Context; use scylla::client::session::Session; use scylla::client::session_builder::SessionBuilder; use std::time::Duration; pub async fn new_session(config: &ConnectionDetails) -> Result { let nodes = config .nodes .iter() .filter(|i| !i.is_empty()) .collect::>(); let session: Session = SessionBuilder::new() .known_nodes(nodes) .connection_timeout(Duration::from_secs(5)) .user(config.username.to_string(), config.password.to_string()) .build() .await .context("Connection Refused. Check your credentials and your IP linked on the ScyllaDB Cloud.")?; Ok(session) } ``` > If the connection is refused, check that your IP address is added to the allowed IPs list in your ScyllaDB Cloud dashboard. Run the app with: ```sh cargo run -- --username scylla --password your-cluster-password \ node-0.aws-sa-east-1.xxx.clusters.scylla.cloud \ node-1.aws-sa-east-1.xxx.clusters.scylla.cloud \ node-2.aws-sa-east-1.xxx.clusters.scylla.cloud ``` ## 3. Handling Queries The ScyllaDB Rust driver supports prepared statements (recommended for repeated queries) and unpaged/iter queries.
\\\\ Use `session.prepare()` to prepare a statement once, then `execute_unpaged` or `execute_iter` to run it. ```rust use futures::TryStreamExt; use scylla::client::session::Session; use scylla::client::session_builder::SessionBuilder; use std::net::IpAddr; use std::time::Duration; #[tokio::main] async fn main() -> anyhow::Result<()> { let session: Session = SessionBuilder::new() .known_nodes(&[ "node-0.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-1.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-2.aws-sa-east-1.xxx.clusters.scylla.cloud", ]) .connection_timeout(Duration::from_secs(30)) .user("scylla", "your-awesome-password") .build() .await .expect("connection refused"); let query = "SELECT address, port, connection_stage FROM system.clients LIMIT 5"; session .query_iter(query, &[]) .await? .rows_stream::<(IpAddr, i32, String)>()? .try_for_each(|row| async move { println!("IP -> {}, Port -> {}, CS -> {}", row.0, row.1, row.2); Ok(()) }) .await?; Ok(()) } ``` Output should look like: ```default IP -> 123.123.123.69, Port -> 61667, CS -> READY IP -> 123.123.123.69, Port -> 62377, CS -> AUTHENTICATING ``` ### 3.1 Creating a Keyspace The `keyspace` in ScyllaDB can be interpreted as your `database`. You can check whether it already exists using the driver’s cluster metadata API and create it if needed. ```rust use anyhow::Result; use scylla::client::session::Session; use scylla::client::session_builder::SessionBuilder; use std::time::Duration; #[tokio::main] async fn main() -> Result<()> { let keyspace = String::from("prod_media_player"); let session: Session = SessionBuilder::new() .known_nodes(&[ "node-0.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-1.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-2.aws-sa-east-1.xxx.clusters.scylla.cloud", ]) .connection_timeout(Duration::from_secs(5)) .user("scylla", "your-password") .build() .await .unwrap(); // Use the driver's metadata API to check if the keyspace already exists. // Normally you'd use CREATE KEYSPACE IF NOT EXISTS, but this showcases the metadata API. let has_keyspace = session .get_cluster_state() .get_keyspace(&keyspace) .is_some(); if !has_keyspace { let create_ks = format!( "CREATE KEYSPACE {} WITH REPLICATION = {{'class': 'NetworkTopologyStrategy', 'replicationFactor': '3'}};", keyspace ); session.query_unpaged(create_ks, &[]).await?; println!("Keyspace {} created!", &keyspace) } else { println!("Keyspace {} already exists!", &keyspace) } Ok(()) } ``` ### 3.2 Creating a Table A table stores your application data. Here we create the `songs` table in the `prod_media_player` keyspace. ```rust use anyhow::Result; use scylla::client::session::Session; use scylla::client::session_builder::SessionBuilder; use std::time::Duration; static KEYSPACE: &str = "prod_media_player"; static TABLE: &str = "songs"; #[tokio::main] async fn main() -> Result<()> { let session: Session = SessionBuilder::new() .known_nodes(&[ "node-0.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-1.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-2.aws-sa-east-1.xxx.clusters.scylla.cloud", ]) .connection_timeout(Duration::from_secs(30)) .user("scylla", "****") .build() .await .expect("connection refused"); // Use the driver's metadata API to check if the table already exists. let has_table = session .get_cluster_state() .get_keyspace(KEYSPACE) .and_then(|ks| ks.tables.get(TABLE)) .is_some(); if !has_table { let create_table = format!( "CREATE TABLE {}.{} ( id uuid, title text, album text, artist text, created_at timestamp, PRIMARY KEY (id, created_at) )", KEYSPACE, TABLE ); session.query_unpaged(create_table, &[]).await?; println!("Table {} created!", TABLE) } else { println!("Table {} already exists!", TABLE) } Ok(()) } ``` ### 3.3 Inserting data Now that we have the keyspace and table, let’s populate it with songs. We use prepared statements for efficiency. ```rust use anyhow::Result; use chrono::Utc; use scylla::client::session::Session; use scylla::client::session_builder::SessionBuilder; use std::str::FromStr; use std::time::Duration; use uuid::Uuid; #[tokio::main] async fn main() -> Result<()> { let keyspace = "prod_media_player"; let table = "songs"; let session: Session = SessionBuilder::new() .known_nodes(&[ "node-0.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-1.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-2.aws-sa-east-1.xxx.clusters.scylla.cloud", ]) .connection_timeout(Duration::from_secs(5)) .user("scylla", "your-password") .build() .await .unwrap(); let now = Utc::now(); let song_list = vec![ ( Uuid::new_v4(), "Stairway to Heaven", "Led Zeppelin IV", "Led Zeppelin", now, ), ( Uuid::from_str("d754f8d5-e037-4898-af75-44587b9cc424").unwrap(), "Glimpse of Us", "Smithereens", "Joji", now, ), (Uuid::new_v4(), "Vegas", "From Movie ELVIS", "Doja Cat", now), ]; let insert_query = format!( "INSERT INTO {}.{} (id, title, album, artist, created_at) VALUES (?,?,?,?,?)", keyspace, table ); let prepared = session.prepare(insert_query).await?; for song in &song_list { session.execute_unpaged(&prepared, song).await?; println!("Inserted: {}", song.1); } Ok(()) } ``` ### 3.4 Reading data List songs from the database using `execute_iter` with a prepared statement: ```rust use anyhow::Result; use chrono::{DateTime, Utc}; use futures::TryStreamExt; use scylla::client::session::Session; use scylla::client::session_builder::SessionBuilder; use std::time::Duration; use uuid::Uuid; #[tokio::main] async fn main() -> Result<()> { let session: Session = SessionBuilder::new() .known_nodes(&[ "node-0.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-1.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-2.aws-sa-east-1.xxx.clusters.scylla.cloud", ]) .connection_timeout(Duration::from_secs(5)) .user("scylla", "your-awesome-password") .build() .await .unwrap(); let prepared = session .prepare( "SELECT id, title, album, artist, created_at FROM prod_media_player.songs LIMIT 10", ) .await?; session .execute_iter(prepared, &[]) .await? .rows_stream::<(Uuid, String, String, String, DateTime)>()? .try_for_each(|row| async move { println!("ID: {} | Song: {} | Album: {} | Created At: {}", row.0, row.1, row.2, row.4); Ok(()) }) .await?; Ok(()) } ``` The result will look like this: ```default ID: d754f8d5-e037-4898-af75-44587b9cc424 | Song: Glimpse of Us | Album: Smithereens | Created At: 2023-08-09 02:11:55 UTC ID: 72f940de-1c8b-46ce-a09d-8c52e66fa21f | Song: Stairway to Heaven | Album: Led Zeppelin IV | Created At: 2023-08-09 02:11:55 UTC ``` ### 3.5 Deleting Data Delete a row by its partition key (`id`). Since `id` is the partition key, this removes all rows with that UUID: ```rust use anyhow::Result; use scylla::client::session::Session; use scylla::client::session_builder::SessionBuilder; use std::str::FromStr; use std::time::Duration; use uuid::Uuid; #[tokio::main] async fn main() -> Result<()> { let session: Session = SessionBuilder::new() .known_nodes(&[ "node-0.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-1.aws-sa-east-1.xxx.clusters.scylla.cloud", "node-2.aws-sa-east-1.xxx.clusters.scylla.cloud", ]) .connection_timeout(Duration::from_secs(5)) .user("scylla", "your-awesome-password") .build() .await .unwrap(); let song_id = Uuid::from_str("d754f8d5-e037-4898-af75-44587b9cc424").unwrap(); let prepared_query = session .prepare("DELETE FROM prod_media_player.songs WHERE id = ?") .await?; session .execute_unpaged(&prepared_query, (song_id,)) .await?; println!("Song deleted!"); Ok(()) } ``` ## 4. Running the Full Application Clone the repository and run the complete Media Player app: ```sh git clone https://github.com/scylladb/scylla-cloud-getting-started.git cd scylla-cloud-getting-started/rust cargo run -- --username scylla --password your-cluster-password \ node-0.aws-sa-east-1.xxx.clusters.scylla.cloud \ node-1.aws-sa-east-1.xxx.clusters.scylla.cloud \ node-2.aws-sa-east-1.xxx.clusters.scylla.cloud ``` Once started, you’ll see a prompt with available commands: ```default ------------------------------------ Here some possibilities !add - add new song !list - list all songs !delete - delete a specific song !stress - stress testing with mocked data !q - quit ------------------------------------ Type any command: ``` ## Conclusion You now have the knowledge to use the basics of ScyllaDB with Rust. If you think something can be improved, please open an issue and let’s make it happen! Did you like the content? Don’t forget to star the repo and follow us on socials. # design-and-data-model.md # Design and Data Model You can learn more about Data Modeling in ScyllaDB (and NoSQL) by taking [this course](https://university.scylladb.com/courses/data-modeling/) on ScyllaDB University. The main goal of data modeling in ScyllaDB is to perform queries fast, even if we sometimes have to duplicate data. Let’s build our schema around the queries we are going to run against our domain entities. When creating the data model, you need to consider both the conceptual data model and the application workflow: which queries will be performed by which users and how often. To achieve that, we want: - Even data distribution - To minimize the number of partitions accessed in a read query. On the other hand, our focus won’t be on avoiding data duplication or minimizing the number of writes. You’re probably familiar with the steps defined here: ![](https://lh5.googleusercontent.com/5JqE89v8KJbSuVsnGswHn83sJOV-tjpeH6r1fqdNl6S77ncqAYb3kIZPSgNI8bqN_43OyZNbHQVpXdqMBFrRmsEvG3JORR302EhMnIb9qa6nuNL7cP2JJDZ4Uon_Pp-QmSCoEQ) ## Conceptual Data Model Starting with the conceptual data model, we need to identify the key entities and the relationships between them. Our application is focused in a single entity called ‘songs’. The concept is to fill with many songs as we want. ![Table Diagram](_static/img/getting-started-diagram.png) ## Application Workflow Next, we move on to the Application Workflow. In this part, we identify the main queries or what questions we will ask the database. This part is important in ScyllaDB, and other NoSQL databases and, as opposed to relational databases is performed early on in the data modeling process. Remember that our data modeling is built around the queries. ## Application Features * Insert a Song * List all songs * Delete a song * Stress Testing ## Queries Now we can detail the above queries in [CQL](https://university.scylladb.com/courses/data-modeling/lessons/basic-data-modeling-2/topic/cql-cqlsh-and-basic-cql-syntax/): Q1: Create a Keyspace ```none CREATE KEYSPACE prod_media_player; ``` Q2: Create a Table ```none CREATE TABLE songs ( id int, title text, album text, artist text, created_at timestamp, updated_at timestamp, PRIMARY KEY (id, updated_at) ); ``` Q3: Insert a new song ```none INSERT INTO prod_media_player.songs (id,title,artist,album,created_at) VALUES (?,?,?,?,?); ``` Q4: List all songs ```none SELECT * FROM songs; ``` Q5: Delete a specific song ```none DELETE FROM songs WHERE id = ? ``` ## Helpful Material Some more advanced topics not covered in this guide are [Collections](https://university.scylladb.com/courses/data-modeling/lessons/advanced-data-modeling/topic/common-data-types-and-collections/), User-Defined[Types](https://university.scylladb.com/courses/data-modeling/lessons/advanced-data-modeling/topic/user-defined-types-udt/) (UDT), expiring data with [time to live (TTL)](https://university.scylladb.com/courses/data-modeling/lessons/advanced-data-modeling/topic/expiring-data-with-ttl-time-to-live/), and [Counters](https://university.scylladb.com/courses/data-modeling/lessons/advanced-data-modeling/topic/counters/). To summarize, when data modeling with ScyllaDB, we have to know our data, think about our queries, pay attention to the primary key and clustering key selection, and not be afraid to duplicate data. # getting-started.md # Getting Started with ScyllaDB Cloud: A sample Media Player App ## Introduction This guide will show you how to create a Cluster into ScyllaDB Cloud, create an Media Player app from scratch and configure it to use ScyllaDB as the backend datastore. It’ll walk you through all the stages of the development process, from gathering requirements to building and running the application. As an example, you will use an application called Media Player. Media Player stores all the tracks that you want to listen and count how many times you listened to. The application consists of three parts: - Validate if you have the Keyspace and Table needed to start storing your tracks/songs; - Access to an REPL with commands to store/list/delete songs; - Run a simple stresser into ScyllaDB Cloud. ## Requirements ### Prerequisites for Deploying the Application The example application uses ScyllaDB Cloud to run a three-node ScyllaDB cluster. You can claim your free ScyllaDB Cloud account [here](https://scylladb.com/cloud). ### Performance Requirements The application has two performance-related parts: sensors that write to the database (throughput sensitive) and a backend dashboard that reads from the database (latency sensitive). * This example assumes 99% writes (songs) and 1% reads (backend dashboard). * SLA: - Writes: throughput of 100K operations per second. - Reads: latency of up to 10 milliseconds for the [99th percentile](https://www.scylladb.com/glossary/low-latency-database/). * The application requires high availability and fault tolerance. Even if a ScyllaDB node goes down or becomes unavailable, the cluster is expected to remain available and continue to provide service. You can learn more about ScyllaDB high availability in [this lesson](https://university.scylladb.com/courses/scylla-essentials-overview/lessons/high-availability/). ### Additional Resources - [ScyllaDB Essentials](https://university.scylladb.com/courses/scylla-essentials-overview/) course on ScyllaDB University. It provides an introduction to ScyllaDB and explains the basics. - [Data Modeling and Application Development](https://university.scylladb.com/courses/data-modeling/) course on ScyllaDB University. It explains basic and advanced data modeling techniques, including information on workflow application, query analysis, denormalization, and other NoSQL data modeling topics. - [ScyllaDB Documentation](https://docs.scylladb.com/) - ScyllaDB users [slack channel](http://slack.scylladb.com/) # getting-started.md # Getting Started with ScyllaDB Cloud + DynamoDB Compatible API: A sample Media Player App with Alternator ## 1. Introduction This guide will show you how to create a cluster in ScyllaDB Cloud, create a Media Player app from scratch, and configure it to use ScyllaDB as the backend datastore. It’ll walk you through all the stages of the development process, from gathering requirements to building and running the application. As an example, you will use an application called Media Player. Media Player stores all the tracks you want to listen to and counts how many times you have listened to them. The application consists of three parts: - Validate if you have the Keyspace and Table needed to start storing your tracks/songs; - Access a REPL with commands to store/list/delete songs; - Run a simple stressor against ScyllaDB Cloud. ## 2. Requirements Before starting your first project with the ScyllaDB Alternator API, we need to set up your environment. ### 2.1 Create a ScyllaDB Cloud account Before you start coding the project, you should create an account at [ScyllaDB Cloud](https://cloud.scylladb.com) or login if you already have one. ![ScyllaDB Cloud Registration Page](_static/img/alternator/getting-started/scylla-registration-page.png) ### 2.2 Create a Cluster After creating and logging in to your ScyllaDB Cloud account, click **Free Trial** (or **New Cluster** > **Dedicated VM**). There, you should: - Give your cluster a name - Select “Amazon DynamoDB API compatible” in the **ScyllaDB API** section - Select the nearest region for your cluster - Keep the default auto-scaling policy. ScyllaDB Cloud scales the cluster automatically, so you don’t need to pick a node size. On the network tab, make sure your IP address is correct and click “Launch Cluster”. ![Cluster creating in progress page](_static/img/alternator/getting-started/scylladb-creating-cluster.png) ## 3. Connecting to your cluster Now that you have the ScyllaDB Alternator running, you will see a panel like the picture below. It will be a three node cluster. ![Cluster Overview page](_static/img/alternator/getting-started/scylladb-cluster-overview.png) As you can see on the first node, the IP “100.51.91.97” will be used to connect to the “Endpoint URL” in any DynamoDB SDK or API. Examples: ```python # Python Example import boto3 alternator_client = boto3.resource('dynamodb',endpoint_url='http://localhost:8000', region_name='None', aws_access_key_id='None', aws_secret_access_key='None') ``` ```php // PHP with AWS SDK use Aws\DynamoDb\DynamoDbClient; $alternatorClient = new DynamoDbClient([ 'endpoint' => "18.231.92.93:8000", // Alternator 'credentials' => ['key' => 'None', 'secret' => 'None'], 'region' => 'None' ]); ``` Now you are able to send requests to your ScyllaDB Alternator instance. Select the SDK for the language of your choice and start building! ## 4. Next Steps Now that you have everything ready, let’s move to the project Design and Data Model with DynamoDB spectations!