Unreal에 SQLite 붙이기 – Extensions

개요

이전에 플러그인을 통해서 SQLite 를 잘 쓰고 있었습니다. 그러던도중 이번에는 SQL 을 작성하는데 특정 함수들이 없다는 에러가 떠서 해결해보고자합니다.

No such acos function exists

이 에러는 라이브러리에서 acos 및 기타 함수를 지원하지 않는다는 것을 의미하고, 이 말은 사용하려면 직접 넣어주어야한다는 뜻이죠. 찾아보니 SQL에서도 extension 을 통해 사용자가 직접 함수를 추가할 수 있게 구성이 되어있었습니다.

다만 문제점은 정적 라이브러리를 빌드해서 해당 파일을 읽어오는 식이었죠.

그래서 저는 함수 호출만으로도 extension 을 추가할 수 있게 수정을 하고 함수를 추가해주었습니다.

플러그인 업데이트

먼저 함수를 추가해주었습니다. 기존에 존재하던 sqlite3_load_extension 함수를 수정해서 동적 라이브러리 로드 부분을 지우고 바로 함수 호출 부분으로 이동하도록 구현했습니다.

/* sqlite3.c */

SQLITE_API int sqlite3_load_extension_manually(sqlite3 *db, sqlite3_loadext_entry fn, char **pzErrMsg) 
{
  void *handle;
  char *zErrmsg = 0;
  void **aHandle;
  int rc;
  
  rc = fn(db, &zErrmsg, &sqlite3Apis);
  if( rc ){
    if( rc==SQLITE_OK_LOAD_PERMANENTLY ) return SQLITE_OK;
    if( pzErrMsg ){
      *pzErrMsg = sqlite3_mprintf("error during initialization: %s", zErrmsg);
    }
    sqlite3_free(zErrmsg);
    return SQLITE_ERROR;
  }

  /* Append the new shared library handle to the db->aExtension array. */
  aHandle = sqlite3DbMallocZero(db, sizeof(handle)*(db->nExtension+1));
  if( aHandle==0 ){
    return SQLITE_NOMEM_BKPT;
  }
  if( db->nExtension>0 ){
    memcpy(aHandle, db->aExtension, sizeof(handle)*db->nExtension);
  }
  sqlite3DbFree(db, db->aExtension);
  db->aExtension = aHandle;

  db->aExtension[db->nExtension++] = handle;
  return SQLITE_OK;
}

이렇게 구성을 했지만 이 함수를 바로 제 프로젝트에서 불러올 순 없었습니다. SQLITE_API 와 플러그인의 CISQLITE3_API 가 달라서 외부에서 접근이 안됬죠. 그래서 USQLiteDatabase를 한번 거쳐 들어가도록 구현했습니다.

/* SQLiteDatabase.cpp */
FString USQLiteDatabase::RegisterExtension(const FString &Target, sqlite3_loadext_entry fn) {
	if (!SQLite3Databases.Contains(Target)) {
		return FString::Printf(TEXT("Unable to find Database By '%s'"), *Target);
	}
	
	auto Db = SQLite3Databases[Target];

	char pzErrMsg[1024];
	sqlite3_load_extension_manually(Db, fn, reinterpret_cast<char**>(&pzErrMsg));

	return pzErrMsg;
}

이후 제 프로젝트 폴더에 각종 함수를 추가하는 c 파일을 추가하고 등록시켜 주니 문제 없이 SQL Command가 동작했습니다.

/* sql_trig.c */
#include "sqlite3ext.h"
SQLITE_EXTENSION_INIT1;
#include <stdlib.h>

/* this bit is required to get M_PI out of MS headers */
#if defined( _WIN32 )
#define _USE_MATH_DEFINES
#endif /* _WIN32 */

#include <math.h>

#define RADIANS(d) (( d / 180.0 ) * M_PI)

static void sql_trig_sin( sqlite3_context *ctx, int num_values, sqlite3_value **values )
{
	double a = RADIANS(sqlite3_value_double( values[0] ));
	sqlite3_result_double( ctx, sin( a ) );
}

static void sql_trig_cos( sqlite3_context *ctx, int num_values, sqlite3_value **values )
{
	double a = RADIANS(sqlite3_value_double( values[0] ));
	sqlite3_result_double( ctx, cos( a ) );
}

static void sql_trig_acos( sqlite3_context *ctx, int num_values, sqlite3_value **values )
{
	double a = sqlite3_value_double( values[0] );
	sqlite3_result_double( ctx, acos( a ) );
}

static void sql_trig_radians( sqlite3_context *ctx, int num_values, sqlite3_value **values )
{
	sqlite3_result_double( ctx, RADIANS(sqlite3_value_double( values[0] ) ));
}


NOVAAIR_API int sqlite3_extension_init( sqlite3 *db, char **error, const sqlite3_api_routines *api )
{
	SQLITE_EXTENSION_INIT2(api);

	sqlite3_create_function( db, "sin",1,
		SQLITE_UTF8, NULL, &sql_trig_sin, NULL, NULL );
	sqlite3_create_function( db, "cos",1,
		SQLITE_UTF8, NULL, &sql_trig_cos, NULL, NULL );
	sqlite3_create_function( db, "acos",1,
		SQLITE_UTF8, NULL, &sql_trig_acos, NULL, NULL );
	sqlite3_create_function( db, "radians",1,
		SQLITE_UTF8, NULL, &sql_trig_radians, NULL, NULL );

	return SQLITE_OK;
}

bool StorageManager::Initialize() {
	...
	auto Error = USQLiteDatabase::RegisterExtension(DB_NAME, sqlite3_extension_init);
	if (!Error.IsEmpty()) {
		UE_LOG(LogDatabase, Error, TEXT("Failed to register extension: %s"), *Error);
	}
	...
}

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *