• SYEDHUSSIM.COM
  • Java
  • Node.js
  • Rust
  • HANA DB
  • SAP Business One
  • Connect To HANA DB Using Rust 21 February 2023 - 77 views
    The hdbconnect package makes it easy to connect to a HANA database using Rust and in this article, we'll take a look at how to establish a connection and perform some queries.

    Connecting To HANA


    Let's start by creating a new binary project and then configuring Cargo.toml to include the hdbconnect dependency.

    cargo new hana_sample
    [dependencies]
    hdbconnect = "0.25"

    Now that hdbconnect has been added as a dependency, we can connect to HANA using the Connection type as shown below in listing 1.

    Listing 1

    use hdbconnect::Connection;
    
    fn main() {
    
        let user = "USER_NAME";
        let password = "PASSWORD";
        let host = "HOST:PORT";
    
        let mut connection = Connection::new(format!("hdbsql://{user}:{password}@{host}")).unwrap();
    }
    

    Executing Queries


    The Connection type has several methods for executing queries, one of which is the query() method. This method takes an SQL statement as an str and returns a Result. If the Result is successful the Ok variant will contain a ResultSet. You can then use the ResultSet's try_into() method to translate the returned data into Rust types that implement serde::Deserialize.

    Let's take a look at a few examples. The query in listing 2 below returns a single row with one value, which is deserialized into a String.

    Listing 2

    ...
    
    let result : String = connection.query("SELECT NOW() FROM DUMMY").unwrap().try_into().unwrap();
    

    Queries that return a single row with multiple columns can be deserialized into a tuple.

    Listing 3

    let result : (String, String) = connection.query("SELECT CURRENT_DATE, CURRENT_TIME FROM DUMMY").unwrap().try_into().unwrap();
    
    println!("{} {}", result.0, result.1);
    

    In the code samples above, date/time values are deserialized into Strings. You can however use the HanaDate/HanaTime types that provide convenient methods to work with dates and times.

    Listing 4

    use hdbconnect::time::{ HanaDate, HanaTime };
    ...
    let result : (HanaDate, HanaTime) = connection.query("SELECT CURRENT_DATE, CURRENT_TIME FROM DUMMY").unwrap().try_into().unwrap();
    
    println!("Day : {}, Hour : {}", result.0.day(), result.1.hour());
    

    Queries that return multiple rows can be deserialized into a vector that can then be used to iterate over the rows.

    Listing 5

    let result : Vec<(String, String)> = connection.query("SELECT SCHEMA_NAME, TABLE_NAME FROM SYS.TABLES LIMIT 5").unwrap().try_into().unwrap();
    
    for row in result{
        println!("{} - {}", row.0, row.1);
    }
    

    Alternatively, as the ResultSet type implements the Iterator trait, we can rewrite the code above as the following.

    Listing 6

    for result in connection.query("SELECT SCHEMA_NAME, TABLE_NAME FROM SYS.TABLES LIMIT 5").unwrap(){
        let data : (String, String) = result.unwrap().try_into().unwrap();
    
        println!("{} - {}", data.0, data.1);
    }
    

    Queries that return null values need to be handled correctly as Rust does not support NULL. For example, the code below will generate an error causing the program to crash with the following error thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Deserialization { source: ValueType("The value cannot be converted into type String") }'

    Listing 7

    let result : String = connection.query("SELECT NULL FROM DUMMY").unwrap().try_into().unwrap();
    

    We can fix this error either by returning a default value using the IFNULL function in the SQL query or deserializing the value into an Option enum type as shown below.

    Listing 8

    // The query returns an empty string if the column value is null
    let result : String = connection.query("SELECT IFNULL(NULL, '') FROM DUMMY").unwrap().try_into().unwrap();
    
    // Deserialize the value into an Option enum type
    let result : Option<String> = connection.query("SELECT NULL FROM DUMMY").unwrap().try_into().unwrap();
    


  • Count File Types In Directory 29 January 2023 - 58 views
    In this last article on developing a command line program, we'll develop a program that counts the occurrence of different file types in a directory and its sub-directories. The program will accept a directory as an argument and recursively scan all sub-directories. We'll use a HashMap to keep track of the count for each file type.

    Let's begin by setting up the main function to collect command line arguments and a HashMap to store the running count for each file type.

    Listing 1

    use std::collections::HashMap;
    
    fn main() {
    
        let args : Vec<String> = env::args().collect();
    
        let mut map : HashMap<String, usize> = HashMap::new();
    }
    

    Rust provides a few handy functions to deal with file system-related tasks, one of those functions is the read_dir() function which returns an iterator over the entries in a directory. The read_dir() function only provides an iterator for the specified directory, so we'll need to write a function that can recursively iterate over all sub-directories.

    To do this, we'll need to know if an entry returned by the iterator is a directory or a file. If it's a directory, we can call our function again with the new directory path.

    Listing 2

    fn read_directory(path : &str){
    
        let dir = fs::read_dir(path).unwrap();
    
        for result in dir{
    
            let entry = &result.unwrap();
            let metadata = entry.metadata();
            
            if metadata.unwrap().is_dir(){
            
                read_directory(entry.path().to_str().unwrap());
            }else{
                // Todo
            }
        }
    }
    

    The read_directory() function in listing 2 above accepts a directory path as an &str, which is then used to read the directory using the read_dir() function from the fs module. The read_dir() function returns an iterator, which we can use to iterator over the entries in the directory. The iterator will yield instances of io::Result<DirEntry>, this is because, during the iteration, new errors can be encountered. After unwrapping the result from the iterator, we get a DirEntry which has a metadata() method.

    The metadata for an entry will tell us if the entry is a file or a directory. If it's a directory, we call the read_directory() method with the path to the directory.

    Note that many of the functions including read_dir() return a Result which should be correctly handled but we'll ignore the errors for this program to keep the code simple.

    Now that we have a function that can recursively iterate over all folders in a directory, all we have to do is pass the HashMap initialized in the main function to the read_directory() function where we can keep track of the count for each file type using the files extension. Listing 3 below shows the complete code.

    Listing 3

    use std::env;
    use std::fs;
    use std::collections::HashMap;
    use std::thread;
    
    fn main() {
    
        let args : Vec<String> = env::args().collect();
    
        let mut map : HashMap<String, usize> = HashMap::new();
    
        read_directory(&args[1], &mut map);
    
        for (ext, count) in map{
            println!("{ext}\t\t{count}");
        }
    }
    
    fn read_directory(path : &str, map : &mut HashMap<String, usize>){
    
        let dir = fs::read_dir(path).unwrap();
    
        for result in dir{
    
            let entry = &result.unwrap();
            let metadata = entry.metadata();
    
            if metadata.unwrap().is_dir(){
                let dir_name = entry.file_name().to_str().unwrap().to_string();
    
                if dir_name.chars().nth(0).unwrap() != '.' {
                    read_dir(entry.path().to_str().unwrap(), map);
                }
            }else{
                let file_name = entry.file_name().to_str().unwrap().to_string();
    
                if file_name.contains("."){
                    let (_file, ext) = file_name.split_once(".").unwrap();
    
                    if map.contains_key(ext){
                        let count = map.get(ext).unwrap_or(&0);
                        let total = *count + 1;
                        map.insert(ext.to_string(), total);
                    }else{
                        map.insert(ext.to_string(), 1);
                    }
                }
            }
        }
    }
    
  • A Simple HTTP Server - ThreadPool - Part 3 15 January 2023 - 160 views
    In part 1, we developed a simple single-threaded HTTP server that processed requests synchronously. In part 2, we modified the server so that each request was handled in a separate thread, which improved concurrency but increased the load on the CPU and memory. In this article, we'll modify the server to use a ThreadPool which will help to improve concurrency while reducing the CPU and memory usage.

    Contents


    A Simple HTTP Server - Part 1
    A Simple HTTP Server - Multi Threading - Part 2
    A Simple HTTP Server - ThreadPool - Part 3

    ThreadPools


    Before we begin developing a ThreadPool, let me explain what a ThreadPool is for those who are not sure. Simply put, a ThreadPool is a collection of per-initialized idle threads that are waiting to do some work. When a thread in the collection receives a task, it executes it, once it's done, the thread goes back to waiting for a new task. Reusing threads in this manner allows us to achieve a greater level of concurrency without burdening the system's resources.

    Let's take the basic design of a ThreadPool and write some code to handle multiple client connections in our HTTP server. We know that we need to create threads and initialize them so that they are ready and waiting to accept a task. A good place to start is to create a custom thread class that inherits from the Thread class.

    Listing 1

    class SocketThread extends Thread{
    
        public void run(){
            // Do work here
        }
    }
    

    The SocketThread class above will need to be initialized and started before the server begins accepting client connections. When the server accepts a new client connection, we need to pass the connection to one of the threads in our pool. We can add a setSocket() method to the SocketThread class and call this method during the accept client connection phase.

    Listing 2

    class SocketThread extends Thread{
    
        public void setSocket(Socket socket){ 
    	// To-Do
        }
        
        ...
    }
    

    The setSocket() method, needs to store the socket in a collection so that it can be queued. The run() method will then pop a socket from the beginning of the queue and continue the client-handling process. We can use a LinkedBlockingQueue to store sockets as this type of queue is thread-safe.

    Listing 3

    class SocketThread extends Thread{
    
        private final LinkedBlockingQueue<Socket> queue;
    
        public SocketThread(){
            this.queue = new LinkedBlockingQueue<Socket>();
        }
    
        public void setSocket(Socket socket){ 
            this.queue.add(socket);
        }
    
        public void run(){
            // Do work here
        }
    }
    


    The run() method gets called when the thread is started. Once the method has finished executing, the thread is terminated. To keep the thread alive, we need to stop the run() method from completing. You may be tempted to use an infinite loop but this will cause the system's CPU to spike. Thankfully, the LinkedBlockingQueue class has a take() method that blocks until an item in the collection becomes available.

    In our case, the take() method will block the SocketThread until a socket is available in the queue. Once the socket becomes available we can continue handling the client request/response. At this point, the thread will no longer be blocked and so it will terminate when the run() method completes. To keep the thread alive, we simply call the run() method again making it a recursive method. Listing 4 below shows the complete SocketThread class which includes handling the client request and response.

    Listing 4

    class SocketThread extends Thread{
    
        private final LinkedBlockingQueue<Socket> queue;
    
        public SocketThread(){
            this.queue = new LinkedBlockingQueue<Socket>();
        }
    
        public void setSocket(Socket socket){ 
            this.queue.add(socket);
        }
    
        public void run(){
            try{
    
                Socket client = this.queue.take();
    
                // Get A BufferedReader/BufferedWriter to handle reading and writing to the stream.
    
                BufferedReader requestReader = 
                            new BufferedReader(
                                new InputStreamReader(client.getInputStream()));
                                
                BufferedWriter responseWriter = 
                            new BufferedWriter(
                                new OutputStreamWriter(client.getOutputStream()));
    
                // Read all the data sent from 
                // the client before we send a response.
    
                while (true){
                    String headerLine = requestReader.readLine();
    
                    if (headerLine.length() == 0){
                        break;
                    }
                }
    
                // How original is this?
                responseWriter.write("Hello World\n");
                responseWriter.flush();
    
                // Closing the client connection will close, both the input and output streams.
                client.close();
    
                this.run();
    
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    

    We can now create several instances of SocketThread to simulate a ThreadPool. Normally a ThreadPool will abstract away the creation of threads and manage an internal array of the threads but I want to keep the code simple, the reason will become clearer later.

    Let's create two instances of SocketThread in the main method and call the start() method on them. When the threads start, the run() method of each thread will wait for a socket.

    Listing 5

    import java.net.*;
    import java.io.*;
    import java.util.concurrent.LinkedBlockingQueue;
    
    class HttpServer{
    
        public static void main(String args[]){
    
            SocketThread thread1 = new SocketThread();
            thread1.start();
    
            SocketThread thread2 = new SocketThread();
            thread2.start();
            
            ...
        }
    }
    

    When the server accepts a socket, we can call the setSocket() method on one of the threads we've created. Since we have two threads, we need to find a way to decide which thread to use. We can solve this problem by alternating between each thread as shown below.

    Listing 6

    
    import java.net.*;
    import java.io.*;
    import java.util.concurrent.LinkedBlockingQueue;
    
    class HttpServer{
    
        public static void main(String args[]){
    
            SocketThread thread1 = new SocketThread();
            thread1.start();
    
            SocketThread thread2 = new SocketThread();
            thread2.start();
    
            try{
                // Create a new server socket and listen on port 9000
                try (ServerSocket server = new ServerSocket(9000)){
    
                    // Continue to listen for client connections
    
                    int i = 0;
                    
                    while (true){
    
                        // Accept a client connection. accept() is a blocking method.
                        Socket client = server.accept();
    
                        if (i % 2 == 0){
                            thread1.setSocket(client);
                        }else{
                            thread2.setSocket(client);
                        }
    
                        i++;
                    }
                }
    
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
    

    An alternative approach to alternating between the threads is to pick a thread with the least number of sockets in the queue. To achieve this, we could expose the size of the queue as a public method and then do a comparison between the two threads.

    Earlier I mentioned that there was a reason why I wanted to keep the code simple and that's because Java has built in classes to help create ThreadPools. In the next article, we'll refactor our code to make use of the ExecutorService API so we won't need to create and manage thread instances ourselves.
  • Port Scanner 05 January 2023 - 112 views
    In the previous article, we developed a simple command line program that counted how many times a word occurred in a text file. The article gave us a brief introduction to the env, collections, and fs modules. In this article, we'll take a look at the TcpStream struct from the net module to develop a simple port scanner.

    Port Scanning


    There are several strategies for scanning ports. The simplest strategy is to try and establish a connection with a host on a particular port. If a connection is made you can assume the port on the host is open. This type of scan is a full connect scan as its comprises of a full TCP handshake.

    We'll use the full connect strategy to develop a simple port scanner program, that will take a host IP and a range of ports to scan as command line arguments.

    Our program will take three command line arguments (Host, IP, a start and stop port). We can use the args() function from the env module to collect these arguments. Recall from the previous article that the args() function always contains one item, which is the name of the program. Knowing this, we can add some basic validation to ensure that the program won't crash if an incorrect number of arguments were supplied.

    Listing 1

    use std::env;
    
    fn main() {
    
        let args : Vec<String> = env::args().collect();
    
        if args.len() == 4 {
    
        }else{
            println!("Command requires 3 arguments");
        }
    }
    

    Now that we have the arguments, we'll need to convert the start and stop ports to an i32 so that we can use them in a while loop. The String type, has a handy parse() method that can convert the value it holds into another type. When using the parse() method, we need to specify the type we want to convert to using the ‘turbofish’: ::<> syntax. Without it, rust won't know which type to convert to.

    The parse() method returns a Result, because it might not be possible to convert to the specified type. For example, trying to parse non numeric characters will result in a error. As such we'll need to handle the parse Results for both the start and stop ports.

    Listing 2

    let ip = &args[1];
    
    if let Ok(start_port) = &args[2].parse::<i32>(){
    
        if let Ok(stop_port) = &args[3].parse::<i32>(){
            // To do
        }else{
            println!("Stop port is not a valid number");
        }
    
    }else{
        println!("Start port is not a valid number");
    }
    

    With the start and stop ports converted to an integer, we can finally construct a while loop that increments a counter that will be used as the port number. On each iteration, we can use the connect method of the TcpStream struct to try and connect to the host on the current port. Listing 3 below contains the full program code.

    Listing 3

    use std::env;
    use std::net::TcpStream;
    
    fn main() {
    
        let args : Vec<String> = env::args().collect();
    
        if args.len() == 4 {
    
            let ip = &args[1];
    
            if let Ok(start_port) = &args[2].parse::<i32>(){
    
                if let Ok(stop_port) = &args[3].parse::<i32>(){
                    
                    let mut port = *start_port;
    
                    while port <= *stop_port{
    
                        let result = TcpStream::connect(format!("{}:{}", ip, port));
    
                        match result{
                            Ok(_stream) => {
                                println!("Port {} open", start_port);
                            },
                            Err(_e) => {}
                        }
    
                        port += 1;
                    }
                }else{
                    println!("Can't parse stop port.")
                }
    
            }else{
                println!("Can't parse start port.")
            }
    
        }else{
            println!("Command requires 3 arguments");
        }
    }
    

    The connect() method takes a single parameter that implements the ToSocketAddrs trait. As String implements this trait, we can use a string literal as an argument. Alternatively we could have used an array of SocketAddr which also implements the ToSocketAddrs trait.

    Because connections can fail, the connect() method returns a Result type as seen in the code above. When the connection is successful, the Ok variant will contain the TCP stream that can be used to read and write to.

    Our program doesn't need to read or write to the stream, so we'll print a message indicating that the port is open. The Err variant is left with a blank implementation because we don't want to print anything when the connection failed.
  • Word Frequency Counter 02 January 2023 - 133 views
    The best way to learn any programming language is to develop small meaningful programs that are easy to write and serve a purpose. In this three part article, we'll develop several small command line programs. Each article will focus on a different part of the standard library.

    Word Frequency Counter


    In this article, we'll develop a simple program that counts how many times a word occurs in a plain text file. The program will accept a command line argument as the filename and output each word with a total count.

    To get command line arguments, we need to use the std::env::args() function. This function returns an iterator that we can use to iterate over each argument. We can call the collect() method on the iterator to convert the arguments into another collection. Listing 1 below converts the iterator into a Vector of type String.

    Listing 1

    use std::env;
    
    fn main(){
    
        let args : Vec<String> = env::args().collect();
    }
    

    The args variable now contains a list of command line arguments. One thing to remember is that the first item in the vector is the name of the program. This means, if we supplied a single argument, the length of args will be 2. As our program only requires one argument, we can use the len() method of the Vector to add some basic validation.

    Listing 2

    ...
        let args : Vec<String> = env::args().collect();
    
        if args.len() == 2{
            // To do
        }else{
            println!("Command requires file name argument");
        }
    

    We can now get references to the items in the args variable either by using the get() method of the Vector or using an index. Once we have a reference to the argument, we can use the fs module to open the file for reading. The easiest way to read a file is to use the read_to_string() function, which returns a Resulttype.

    Listing 3

            let file_name = &args[1];
    
            let result = fs::read_to_string(file_name);
    

    As read_to_string() returns a Result type, we can use pattern matching to handle the Ok and Err variants. If the result is successful, the Ok variant will supply us with the file contents. Once we have the data, we can split it using the space character to get a collection of individual words.

    The easiest way to count the occurrence of a word is to store each word in a HashMap. We can do this by iterating over the words and adding each word to the HashMap with an initial value of 1. On each iteration, we can check if the current word exists in the HashMap, if it does, we can update the counter. Listing 4 below shows the complete code.

    Listing 4

    use std::env;
    use std::fs;
    use std::collections::HashMap;
    
    fn main(){
    
        let args : Vec<String> = env::args().collect();
    
        if args.len() == 2{
    
            let file_name = &args[1];
    
            let result = fs::read_to_string(file_name);
    
            match result {
                Ok(s) => {
    
                    let mut word_map : HashMap<&str, i32> = HashMap::new();
                    
                    let words = s.split(" ");
    
                    for word in words{
                        
                        if word_map.contains_key(word){
                            let count = word_map.get(word).unwrap_or(&0);
                            let total = *count + 1;
                            word_map.insert(word, total);
                        }else{
                            word_map.insert(word, 1);
                        }
                    }
    
                    for (k, v) in word_map{
                        println!("{} = {}", k, v);
                    }
                },
                Err(e) => {
                    println!("{}", e);
                }
            }
        }else{
            println!("Command requires file name argument");
        }
    }
    

    Few things to note about the code above. map.get(..) returns an Option<&i32>, we could have used match to handle Some and None but instead, we've used the unwrap_or() method to return a default value. Since the value contained in Some is a reference to an i32, unwrap_or() must also return a reference to an i32.

    The count variable is a reference to a value, this means we need to dereference it before using it in the addition. Once the HashMap has been constructed, we can iterate over the items and print each word and its count.