pretty

Sunday 2 July 2023

Running Docker MySQL container on MacOS

1. Launch docker desktop server on MacOS via Launchpad icon.

Docker desktop application can be downloaded from the docker web site.
After launch the docker icon should appear on the right top icon tray of the menu. Docker can be used from command line now, lets check the docker is running and the version.

docker --version
Docker version 23.0.5, build bc4487a

1.1 Get docker mysql image.
docker run mysql:latest

2. Launch MySQL docker container.
docker run -p 3306:3306 -e MYSQL_ROOT_PASSWORD=pwd -d mysql
Arguments:
-p publish port from container and map it to host OS port, this is to connect to MySQL from the outside environment later.
-e env variables, used only MYSQL_ROOT_PASSWORD in this case and set a new 'pwd' for the root user. Optionally, we can create more users with corresponding passwords on this step, providing more env parameters like this:
docker run -p 3306:3306 -e MYSQL_ROOT_PASSWORD=pwd MYSQL_USER=newuser MYSQL_PASSWORD newpwd -d mysql
-d daemon mode
-'mysql' would be a new container name

2.1 Check that container is running and get its id from table CONTAINER ID.
docker ps
CONTAINER ID   IMAGE     COMMAND                  CREATED          STATUS          PORTS                               NAMES
7fc7ee05859d   mysql     "docker-entrypoint.s…"   26 minutes ago   Up 26 minutes   0.0.0.0:3306->3306/tcp, 33060/tcp   angry_fermi

3. Connect to mysql container by CONTAINER ID (get it from previous step) using docker shell.
docker exec -it 7fc7ee05859d bash
Arguments:
-it interactive mode (shell)
-'7fc7ee05859d' is a container id
Now, inside the container, we can launch mysql console providing the root user name and actual password that was set on step 2.
mysql -uroot -p       
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 28
Server version: 8.0.33 MySQL Community Server - GPL

Copyright (c) 2000, 2023, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.00 sec)

3.1 Lets check that MySQL is really available to the outside of the container, as we mapped internal port of the container to OS port 3306 on step 2.

One of the options to do this is to install MySQL workbench application from Oracle on the host computer. When launched it allows to create a new MySQL Connection. Supplying 127.0.0.1:3306 for this new connection (that is localhost:port, that we mapped to on step 2), we should be able to connect to the DB inside the docker container.

Saturday 15 April 2023

Inspect sqlite database file from iOS emulator

1. View sqlite file location.

Launch XCode and follow these steps:
- XCode Menu Bar > Product > Scheme > Edit Scheme
- Run option
- Add or modify the Arguments passed on launch to contain -com.apple.CoreData.SQLDebug 1
- Launch the application and wait for Output to have a string with db location, like:

CoreData: annotation: Connecting to sqlite database file at "/Users/user/Library/Developer/CoreSimulator/Devices/DA54BPCB-39F1-4D19-888A-FA146477606DD/data/Containers/Data/Application/567443-7273-4B75-BFDA-86756/Library/Application Support/Data.sqlite"

2. Knowing the location, open Terminal and cd into SQlite db location. It may look like this:

cd "/Users/user/Library/Developer/CoreSimulator/Devices/DA54BPCB-39F1-4D19-888A-FA146477606DD/data/Containers/Data/Application/567443-7273-4B75-BFDA-86756/Library/Application Support/"

Copy 3 database files from this directory to desktop, or some other convenient directory:
cp Data.sqlite /Users/user/Desktop
cp Data.sqlite-shm /Users/user/Desktop
cp Data.sqlite-wal /Users/user/Desktop

3. Open the copied Data.sqlite from /Desktop with apps from AppStore, like Ridill SQLite or SQLiteFlow.

Monday 6 February 2023

Kotlin return statement in anonymous function vs inlined lambda functions

Having a return statement in an anonymous function in Kotlin acts like a break statement. The return statement without a @label always returns from the nearest function declared with a 'fun' keyword (Anonymous function doc).

On the other hand, return statement inside a lambda function, that was passed to an inline function, exits the enclosing outer function. The lambda is inlined, so the return inside it is actually treated as a return from outer function scope.

Saturday 4 February 2023

Read and update value inside transaction in Room DB

    In the post Accessing CoreData from a single background context there is a description of the process to safely update the value in CoreData on iOS. Single background context was used to guarantee that there would be no merge conflicts during update.

    To achieve such result when using a Room DB on Android, the similar approach can also be used. The database writes may be done on a single thread. However, as Room DB is a object relational mapping library, it does not abstract away the underlying SQLite database concepts. Instead of using single thread for writing into DB we may use SQL transaction mechanism. Let's check how this works, employing a simple unit test.

    For testing purpose, there would be two database tables, Tag and PageNumber, with a one-to-one relationship. Each Tag in a database may contain a corresponding unique PageNumer entity, with the 'page' value, indicating the currently active page number for that tag.

@Entity
data class Tag(
    @PrimaryKey(autoGenerate = false)
    val name: String
)

@Entity(
    foreignKeys = [ForeignKey(
        entity = Tag::class,
        parentColumns = arrayOf("name"),
        childColumns = arrayOf("tagName"),
        onUpdate = ForeignKey.CASCADE,
        onDelete = ForeignKey.CASCADE
    )]
)
data class PageNumber(
    @PrimaryKey
    val tagName: String,
    val page: Int
) 


    In the following Data Access Object function that does read-and-update process is called getAndIncrementPageNumberForTag() and has a @Transaction annotation, placing it's body inside a SQLite transaction.

@Dao
interface TagsDao {
	
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    suspend fun insertTag(tag: Tag)

    // Inserts new PageNumber or Updates it if it already exists (available since Room 2.5.0)
    @Upsert
    suspend fun upsertPageNumber(pageNumber: PageNumber)

    // Loads the PageNumber for Tag
    @Query("SELECT * FROM tag JOIN pageNumber ON tag.name = pageNumber.tagName")
    suspend fun loadPageNumberForTag(): Map>

    // Gets the current page number for the tag, incrementing this number and saving it afterwards.
    @Transaction
    suspend fun getAndIncrementPageNumberForTag(tagName: String, defaultPageCount: Int): Int {
        val tag = Tag(tagName)
        
        // Inserts new tag if it does not yet exist
        insertTag(tag) 
        
        // Load PageNumber for this tag
        val pageNumber = loadPageNumberForTag()[tag]?.firstOrNull() ?: PageNumber(tagName, defaultPageCount)
        val result = pageNumber.page
        
        // Increment the PageNumber.page value
        upsertPageNumber(PageNumber(tagName, result + 1))
        
        // Return an old PageNumber.page value
        return result
    }
}


  Following is an instrumented unit test to check that all increments took place without interfering with each other.

@RunWith(AndroidJUnit4::class)
class DbTest {
    private lateinit var tagsDao: TagsDao
    private lateinit var db: GuessDatabase

    @Before
    fun createDb() {
        val context = ApplicationProvider.getApplicationContext()
        db = Room.inMemoryDatabaseBuilder(
            context, GuessDatabase::class.java
        ).build()
        tagsDao = db.tagsDao
    }

    @After
    @Throws(IOException::class)
    fun closeDb() {
        db.close()
    }

    @Test
    fun test() = runBlocking {
        var pageNumber = 0
        val cnt = 10
        
        withContext(Dispatchers.Default) {
            repeat(cnt) {
                launch {
                    pageNumber = tagsDao.getAndIncrementPageNumberForTag("tagName", 1)
                }
            }
        }

        Assert.assertEquals(pageNumber, cnt)
    }
}


    If, however, the @Transaction annotation would be removed, the test will fail. Without single transaction, the function getAndIncrementPageNumberForTag() accessed simultaneously from multiple threads from Dispatchers.Default pool would put incorrect results into database due to the race conditon.

    Would putting the limit of 1 active thread on a Dispatchers.Default pool - like Dispatchers.Default.limitedParallelism(1), also protect from the race condition, just like a transaction did? In this case - no. The function getAndIncrementPageNumberForTag() would be called on a same worker actually, but the functions that it invokes - insertTag(), upsertPageNumber(), ... are all a suspend functions with implementations provided by Room library, and they would anyway run on different workers. This is why the approach with limitedParallelism(1) call on dispatcher would also require removing the suspend modifier from mentioned functions.

    Summing up, in Room DB the SQL transaction mechanism may be used to prevent the merge conflicts during read and update data operations.

Monday 9 January 2023

Parsing XML response from service on Android without using extra libraries

The XML response from REST services is not commonplace, and today it is problematic to parse it using Retrofit or Ktor on Android. While Retrofit has the SimpleXmlConverterFactory, this library is deprecated, and no viable alternatives exist. Ktor on the other hand, has XML Converter only server side.

The solution presented in this code snippet uses built-in Android DocumentBuilder to process the XML. This class is available in Android since API Level 1. In the following sample, the application needs to fetch a list of photos from Flickr service. This service provides the XML formatted response. We are only interested in populating our FlickrPhotoModel data classes with the attributes from <photo> nodes.


While this approach may not be ideal if we need to work extensively with XML API, it is a proper fallback to use for just a bunch of requests.