From b49bc1a44fca15cf215462c292fec1d76fa48882 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@legacyworlds.com>
Date: Tue, 10 Jan 2012 12:30:47 +0100
Subject: [PATCH] Planet resources

* Added missing table that should store a planet's resources data
(income and upkeep for each type of resource).

* Modified resource definition functions and universe generator to
initialise planet resource records as well

* Heavy clean-up in resource definition function unit tests
---
 .../parts/030-data/100-universe.sql           |  46 +++
 .../parts/040-functions/025-resources.sql     |  56 +++-
 .../parts/040-functions/060-universe.sql      |   4 +
 .../100-universe/020-planet-resources.sql     | 156 +++++++++
 .../005-add-resource-records.sql              |  39 +++
 .../010-uoc-resource-internal.sql             |  32 +-
 .../025-resources/020-uoc-resource.sql        | 131 ++------
 .../025-resources/030-uoc-natres-internal.sql |  30 +-
 .../040-uoc-natural-resource.sql              | 307 +++---------------
 .../020-verse-planet-resources.sql            |  28 ++
 .../005-add-resource-records.sql              |  14 +
 11 files changed, 465 insertions(+), 378 deletions(-)
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/030-data/100-universe/020-planet-resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/005-add-resource-records.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/030-data/100-universe/020-verse-planet-resources.sql
 create mode 100644 legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/005-add-resource-records.sql

diff --git a/legacyworlds-server-data/db-structure/parts/030-data/100-universe.sql b/legacyworlds-server-data/db-structure/parts/030-data/100-universe.sql
index 58ddf52..2453ded 100644
--- a/legacyworlds-server-data/db-structure/parts/030-data/100-universe.sql
+++ b/legacyworlds-server-data/db-structure/parts/030-data/100-universe.sql
@@ -135,6 +135,52 @@ ALTER TABLE verse.planet_money
 		FOREIGN KEY (planet_id) REFERENCES verse.planets;
 
 
+
+/*
+ * Planet resource changes
+ * ------------------------
+ * 
+ * This table stores the results of planet resource updates. It will then be
+ * used to update the owning empires' resources.
+ * 
+ * This table applies to both basic and natural resources. 
+ */
+CREATE TABLE verse.planet_resources(
+
+	/* Identifier of the planet */
+	planet_id			INT NOT NULL ,
+
+	/* Identifier of the resource type */
+	resource_name_id	INT NOT NULL ,
+
+	/* Quantity of that resource which was somehow gained at the last game
+	 * update.
+	 */
+	pres_income			DOUBLE PRECISION NOT NULL
+							DEFAULT 0
+							CHECK( pres_income >= 0 ) ,
+
+	/* Quantity of that resource used by the planet's various buildings at the
+	 * last game update.
+	 */
+	pres_upkeep			DOUBLE PRECISION NOT NULL
+							DEFAULT 0
+							CHECK( pres_upkeep >= 0 ) ,
+
+	/* Primary key on (planet,type of resource) */
+	PRIMARY KEY( planet_id , resource_name_id )
+);
+
+CREATE INDEX idx_pres_resource
+	ON verse.planet_resources( resource_name_id );
+
+ALTER TABLE verse.planet_resources
+	ADD CONSTRAINT fk_pres_planet
+		FOREIGN KEY ( planet_id ) REFERENCES verse.planets ,
+	ADD CONSTRAINT fk_pres_resource
+		FOREIGN KEY ( resource_name_id ) REFERENCES defs.resources;
+
+
 --
 -- Buildings
 --
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql b/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
index 0937af2..3a47212 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/025-resources.sql
@@ -38,6 +38,35 @@ CREATE TYPE defs.resource_update_result
 	);
 
 
+/*
+ * Add a resource to all empire and planet records
+ * 
+ * This function makes sure that all empire and planet records have a row for
+ * a newly-created resource.
+ * 
+ * Parameters:
+ *		_resource		The identifier of the resource to add
+ */
+DROP FUNCTION IF EXISTS defs.add_resource_records( INT );
+CREATE FUNCTION defs.add_resource_records( _resource INT )
+	RETURNS VOID
+	STRICT VOLATILE
+	SECURITY INVOKER
+AS $$
+
+	INSERT INTO emp.resources ( empire_id , resource_name_id )
+			SELECT name_id , $1 FROM emp.empires;
+
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
+			SELECT name_id , $1 FROM verse.planets;
+
+$$ LANGUAGE SQL;
+
+REVOKE EXECUTE
+	ON FUNCTION defs.add_resource_records( INT )
+	FROM PUBLIC;
+
+
 
 /*
  * Create or update a basic resource
@@ -60,7 +89,8 @@ CREATE TYPE defs.resource_update_result
  * Returns:
  *		?				the result code for the operation
  */
-CREATE OR REPLACE FUNCTION defs.uoc_resource_internal(
+DROP FUNCTION IF EXISTS defs.uoc_resource_internal( TEXT , TEXT , TEXT , INT );
+CREATE FUNCTION defs.uoc_resource_internal(
 				_name TEXT ,
 				_description TEXT ,
 				_category TEXT ,
@@ -103,11 +133,7 @@ BEGIN
 			_name_id , _desc_id , _cat_id , _weight
 		);
 
-		-- Add the resource to all empires
-		INSERT INTO emp.resources ( empire_id , resource_name_id )
-			SELECT name_id , _name_id
-				FROM emp.empires;
-
+		PERFORM defs.add_resource_records( _name_id );
 		RETURN 'CREATED';
 	EXCEPTION
 		WHEN unique_violation THEN
@@ -163,7 +189,8 @@ REVOKE EXECUTE
  * Returns:
  *		?				the result code for the operation
  */
-CREATE OR REPLACE FUNCTION defs.uoc_resource(
+DROP FUNCTION IF EXISTS defs.uoc_resource( TEXT , TEXT , INT );
+CREATE FUNCTION defs.uoc_resource(
 			_name TEXT ,
 			_description TEXT ,
 			_weight INT )
@@ -196,7 +223,8 @@ GRANT EXECUTE
  * Returns:
  *		?				the result code for the operation
  */
-CREATE OR REPLACE FUNCTION defs.uoc_resource(
+DROP FUNCTION IF EXISTS defs.uoc_resource( TEXT , TEXT , TEXT , INT );
+CREATE FUNCTION defs.uoc_resource(
 			_name TEXT ,
 			_description TEXT ,
 			_category TEXT ,
@@ -246,7 +274,11 @@ GRANT EXECUTE
  * Returns:
  *		?				the result code for the operation
  */
-CREATE OR REPLACE FUNCTION defs.uoc_natres_internal(
+DROP FUNCTION IF EXISTS defs.uoc_natres_internal( TEXT ,  TEXT , TEXT , INT ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION , DOUBLE PRECISION , DOUBLE PRECISION ,
+			DOUBLE PRECISION );
+CREATE FUNCTION defs.uoc_natres_internal(
 				_name TEXT ,
 				_description TEXT ,
 				_category TEXT ,
@@ -319,11 +351,7 @@ BEGIN
 			_recovery_avg , _recovery_dev
 		);
 
-		-- Add the resource to all empires
-		INSERT INTO emp.resources ( empire_id , resource_name_id )
-			SELECT name_id , _name_id
-				FROM emp.empires;
-
+		PERFORM defs.add_resource_records( _name_id );
 		RETURN 'CREATED';
 	END IF;
 
diff --git a/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql b/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
index 1a135ea..67e58c1 100644
--- a/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
+++ b/legacyworlds-server-data/db-structure/parts/040-functions/060-universe.sql
@@ -193,6 +193,10 @@ BEGIN
 					verse.get_raw_production( pnid , 'CASH'::building_output_type )
 				) , verse.get_planet_upkeep( pnid ) );
 
+	-- FIXME: for now, just stick data about resources in the appropriate table
+	INSERT INTO verse.planet_resources ( planet_id , resource_name_id )
+		SELECT pnid , resource_name_id FROM defs.resources;
+
 	-- Add planet update records
 	FOR utp IN SELECT x FROM unnest( enum_range( NULL::update_type ) ) AS x
 					WHERE x::text LIKE 'PLANET_%'
diff --git a/legacyworlds-server-data/db-structure/tests/admin/030-data/100-universe/020-planet-resources.sql b/legacyworlds-server-data/db-structure/tests/admin/030-data/100-universe/020-planet-resources.sql
new file mode 100644
index 0000000..e20ba17
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/030-data/100-universe/020-planet-resources.sql
@@ -0,0 +1,156 @@
+/*
+ * Test constraints and foreign keys on verse.planet_resources
+ */
+BEGIN;
+	/* We need a pair of resource definitions and a pair of planets. */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	\i utils/accounts.sql
+	\i utils/naming.sql
+	\i utils/universe.sql
+	SELECT _create_natural_resources( 2 , 'testResource' );
+	SELECT _create_raw_planets( 2 , 'testPlanet' );
+
+	/****** TESTS BEGIN HERE ******/
+	SELECT plan( 16 );
+	
+	SELECT diag_test_name( 'verse.planet_resources - Valid record' );
+	SELECT lives_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_income , pres_upkeep
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			0 , 0
+		); $$
+	);
+	DELETE FROM verse.planet_resources;
+
+	SELECT diag_test_name( 'verse.planet_resources - Inserting with default values' );
+	SELECT lives_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' )
+		); $$
+	);
+	SELECT diag_test_name( 'verse.planet_resources - Default income = 0' );
+	SELECT is( pres_income , 0::DOUBLE PRECISION )
+		FROM verse.planet_resources;
+	SELECT diag_test_name( 'verse.planet_resources - Default upkeep = 0' );
+	SELECT is( pres_upkeep , 0::DOUBLE PRECISION )
+		FROM verse.planet_resources;
+	
+	SELECT diag_test_name( 'verse.planet_resources - Duplicate record' );
+	SELECT throws_ok($$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' )
+		); $$ , 23505 );
+	
+	SELECT diag_test_name( 'verse.planet_resources - Different planet, same resource' );
+	SELECT lives_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet2' ) , _get_string( 'testResource1' )
+		); $$
+	);
+	
+	SELECT diag_test_name( 'verse.planet_resources - Different resource, same planet' );
+	SELECT lives_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource2' )
+		); $$
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - Different planet and resource' );
+	SELECT lives_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet2' ) , _get_string( 'testResource2' )
+		); $$
+	);
+	DELETE FROM verse.planet_resources;
+
+	SELECT diag_test_name( 'verse.planet_resources - NULL planet identifier' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			NULL , _get_string( 'testResource1' )
+		); $$ , 23502
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - NULL resource identifier' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , NULL
+		); $$ , 23502
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - NULL income' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_income
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			NULL
+		); $$ , 23502
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - NULL upkeep' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_upkeep
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			NULL
+		); $$ , 23502
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - Invalid planet identifier' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_bad_map_name( ) , _get_string( 'testResource1' )
+		); $$ , 23503
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - Invalid resource identifier' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_bad_string( )
+		); $$ , 23503
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - Income < 0' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_income
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			-1
+		); $$ , 23514
+	);
+
+	SELECT diag_test_name( 'verse.planet_resources - Upkeep > 0' );
+	SELECT throws_ok( $$
+		INSERT INTO verse.planet_resources(
+			planet_id , resource_name_id , pres_upkeep
+		) VALUES (
+			_get_map_name( 'testPlanet1' ) , _get_string( 'testResource1' ) ,
+			-1
+		); $$ , 23514
+	);
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/005-add-resource-records.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/005-add-resource-records.sql
new file mode 100644
index 0000000..58ba524
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/005-add-resource-records.sql
@@ -0,0 +1,39 @@
+/*
+ * Test the defs.add_resource_records() function
+ */
+BEGIN;
+	/* Remove foreign keys from empires and planets, then insert dummy records
+	 * into both tables.
+	 */
+	ALTER TABLE emp.empires
+		DROP CONSTRAINT fk_empires_name;
+	INSERT INTO emp.empires ( name_id , cash )
+		VALUES ( 1 , 0 );
+
+	ALTER TABLE verse.planets
+		DROP CONSTRAINT fk_planets_name ,
+		DROP CONSTRAINT fk_planets_system;
+	INSERT INTO verse.planets ( name_id , system_id , orbit , picture , population )
+		VALUES ( 1 , 1 , 1 , 1 , 1 );
+
+	/* Create a resource type and call the function */
+	\i utils/strings.sql
+	\i utils/resources.sql
+	SELECT _create_resources( 1 , 'test' );
+	SELECT defs.add_resource_records( _get_string( 'test1' ) );
+	
+	/***** TESTS BEGIN HERE *****/
+	SELECT no_plan( );
+
+	SELECT diag_test_name( 'defs.add_resource_records() - Empire record created' );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM emp.resources
+		WHERE empire_id = 1 AND resource_name_id = _get_string( 'test1' );
+
+	SELECT diag_test_name( 'defs.add_resource_records() - Planet record created' );
+	SELECT is( COUNT(*)::INT , 1 )
+		FROM verse.planet_resources
+		WHERE planet_id = 1 AND resource_name_id = _get_string( 'test1' );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql
index 9dc1a03..2c2d08c 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/010-uoc-resource-internal.sql
@@ -10,7 +10,7 @@ BEGIN;
 	SELECT _create_natural_resources( 1 , 'natRes' );
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 16 );
+	SELECT plan( 17 );
 
 	SELECT diag_test_name( 'defs.uoc_resource_internal() - creation without category' );
 	SELECT is( defs.uoc_resource_internal( 'test3' , 'test4' , NULL , 1 ) , 'CREATED' );
@@ -70,20 +70,38 @@ BEGIN;
 	SELECT diag_test_name( 'defs.uoc_resource_internal() - weight <= 0' );
 	SELECT is( defs.uoc_resource_internal( 'test1' , 'test2' , NULL , 0 ) , 'BAD_VALUE' );
 	
-	/* Reset resources, create empire */
+	/* Reset resources, create temporary table, replace
+	 * defs.add_resource_records() to make sure it's called when needed (and
+	 * only then)
+	 */
 	DELETE FROM defs.natural_resources;
 	DELETE FROM defs.resources;
+	CREATE TEMPORARY TABLE arr_called( on_id INT ) ON COMMIT DROP;
+	CREATE OR REPLACE FUNCTION defs.add_resource_records( _resource INT )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $$
+		INSERT INTO arr_called VALUES( $1 );
+	$$ LANGUAGE SQL;
 	ALTER TABLE emp.empires
 		DROP CONSTRAINT fk_empires_name;
 	INSERT INTO emp.empires ( name_id , cash )
 		VALUES ( 1 , 0 );
 
-	SELECT diag_test_name( 'defs.uoc_resource_internal() - new resources are added to empires' );
-	SELECT defs.uoc_resource_internal( 'test3' , 'test4' , NULL , 1 );
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - defs.add_resource_record() called on new resource' );
+	SELECT defs.uoc_resource_internal( 'test1' , 'test4' , NULL , 1 );
 	SELECT is( COUNT(*)::INT , 1 )
-		FROM emp.resources
-		WHERE empire_id = 1
-			AND resource_name_id = _get_string( 'test3' );
+		FROM arr_called
+		WHERE on_id = _get_string( 'test1' );
+	DELETE FROM arr_called;
+
+	SELECT diag_test_name( 'defs.uoc_resource_internal() - defs.add_resource_record() not called on resource update' );
+	SELECT defs.uoc_resource_internal( 'test1' , 'test3' , NULL , 1 );
+	SELECT is( COUNT(*)::INT , 0 )
+		FROM arr_called
+		WHERE on_id = _get_string( 'test1' );
+	DELETE FROM arr_called;
 
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql
index 3b08cfd..aa6af61 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/020-uoc-resource.sql
@@ -2,110 +2,45 @@
  * Test the defs.uoc_resource() functions
  */
 BEGIN;
-	
-	/* We need a few strings to be used when creating resource definitions, and a natural resource. */
-	\i utils/strings.sql
-	\i utils/resources.sql
-	SELECT _create_test_strings( 7 );
-	SELECT _create_natural_resources( 1 , 'natRes' );
+	/* Create a fake internal function that will just push its parameters to
+	 * a temporary table.
+	 */
+	CREATE TEMPORARY TABLE calls_to_internal(
+		name TEXT , description TEXT , category TEXT , weight INT 
+	) ON COMMIT DROP;
+	CREATE OR REPLACE FUNCTION defs.uoc_resource_internal(
+				_name TEXT ,
+				_description TEXT ,
+				_category TEXT ,
+				_weight INT )
+		RETURNS defs.resource_update_result
+		CALLED ON NULL INPUT
+		VOLATILE
+		SECURITY INVOKER
+	AS $$
+		INSERT INTO calls_to_internal VALUES (
+			$1 , $2 , CASE WHEN $3 IS NULL THEN 'NULL' ELSE $3 END , $4
+		);
+		SELECT 'BAD_STRINGS'::defs.resource_update_result;
+	$$ LANGUAGE SQL;
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 24 );
+	SELECT plan( 2 );
 
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL name (no category)' );
-	SELECT is( defs.uoc_resource( NULL , 'test2' , 1 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL description (no category)' );
-	SELECT is( defs.uoc_resource( 'test1' , NULL , 1 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL weight (no category)' );
-	SELECT is( defs.uoc_resource( 'test1' , 'test2' , NULL ) , NULL );
-
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL name (with category)' );
-	SELECT is( defs.uoc_resource( NULL , 'test2' , 'test3' , 1 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL description (with category)' );
-	SELECT is( defs.uoc_resource( 'test1' , NULL , 'test3' , 1 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL category' );
-	SELECT is( defs.uoc_resource( 'test1' , 'test2' , NULL , 1 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_resource() - NULL weight (with category)' );
-	SELECT is( defs.uoc_resource( 'test1' , 'test2' , 'test3' , NULL ) , NULL );
-
-	SELECT diag_test_name( 'defs.uoc_resource() - creation without category' );
-	SELECT is( defs.uoc_resource( 'test3' , 'test4' , 1 ) , 'CREATED' );
-	SELECT diag_test_name( 'defs.uoc_resource() - creation results without category' );
-	SELECT results_eq(
-		$$ SELECT resource_name_id , resource_description_id , resource_weight
-				FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test3' )
-					AND resource_category_id IS NULL $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , 1 ) $$
+	SELECT diag_test_name( 'defs.uoc_resource() - Calls internal function (without category)' );
+	SELECT defs.uoc_resource( 'a' , 'b' , 1 );
+	SELECT set_eq(
+		$$ SELECT * FROM calls_to_internal; $$ ,
+		$$ VALUES( 'a' , 'b' , 'NULL' , 1 ) $$
 	);
+	DELETE FROM calls_to_internal;
 
-	SELECT diag_test_name( 'defs.uoc_resource() - creation with category' );
-	SELECT is( defs.uoc_resource( 'test5' , 'test6' , 'test7' , 1 ) , 'CREATED' );
-	SELECT diag_test_name( 'defs.uoc_resource() - creation results with category' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test5' ) $$ ,
-		$$ VALUES ( _get_string( 'test5' ) , _get_string( 'test6' ) , _get_string( 'test7' ) , 1 ) $$
+	SELECT diag_test_name( 'defs.uoc_resource() - Calls internal function (without category)' );
+	SELECT defs.uoc_resource( 'a' , 'b' , 'c' , 1 );
+	SELECT set_eq(
+		$$ SELECT * FROM calls_to_internal; $$ ,
+		$$ VALUES( 'a' , 'b' , 'c' , 1 ) $$
 	);
 
-	SELECT diag_test_name( 'defs.uoc_resource() - update without category' );
-	SELECT is( defs.uoc_resource( 'test3' , 'test7', 2 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_resource() - update results without category' );
-	SELECT results_eq(
-		$$ SELECT resource_name_id , resource_description_id , resource_weight
-				FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test3' )
-					AND resource_category_id IS NULL $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test7' ) , 2 ) $$
-	);
-
-	SELECT diag_test_name( 'defs.uoc_resource() - update with category' );
-	SELECT is( defs.uoc_resource( 'test3' , 'test4' , 'test7' , 1 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_resource() - update results with category' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , _get_string( 'test7' ) , 1 ) $$
-	);
-
-	SELECT diag_test_name( 'defs.uoc_resource() - incorrect name string' );
-	SELECT is( defs.uoc_resource( 'does-not-exist' , 'test2' , 1 ) , 'BAD_STRINGS' );
-	SELECT diag_test_name( 'defs.uoc_resource() - incorrect description string' );
-	SELECT is( defs.uoc_resource( 'test1' , 'does-not-exist' , 1 ) , 'BAD_STRINGS' );
-	SELECT diag_test_name( 'defs.uoc_resource() - incorrect category string' );
-	SELECT is( defs.uoc_resource( 'test1' , 'test2' , 'does-not-exist' , 1 ) , 'BAD_STRINGS' );
-
-	SELECT diag_test_name( 'defs.uoc_resource() - duplicate description on new resource' );
-	SELECT is( defs.uoc_resource( 'test1' , 'test4' , 1 ) , 'DUP_DESCR' );
-	SELECT diag_test_name( 'defs.uoc_resource() - duplicate description on existing resource' );
-	SELECT is( defs.uoc_resource( 'test5' , 'test4' , 1 ) , 'DUP_DESCR' );
-
-	SELECT diag_test_name( 'defs.uoc_resource() - update on natural resource' );
-	SELECT is( defs.uoc_resource( 'natRes1' , 'test2' , 1 ) , 'BAD_TYPE' );
-
-	SELECT diag_test_name( 'defs.uoc_resource() - weight <= 0' );
-	SELECT is( defs.uoc_resource( 'natRes1' , 'test2' , 0 ) , 'BAD_VALUE' );
-
-	/* Reset resources, create empire */
-	DELETE FROM defs.natural_resources;
-	DELETE FROM defs.resources;
-	ALTER TABLE emp.empires
-		DROP CONSTRAINT fk_empires_name;
-	INSERT INTO emp.empires ( name_id , cash )
-		VALUES ( 1 , 0 );
-
-	SELECT diag_test_name( 'defs.uoc_resource() - new resources are added to empires (no category)' );
-	SELECT defs.uoc_resource( 'test1' , 'test2' , 1 );
-	SELECT is( COUNT(*)::INT , 1 )
-		FROM emp.resources
-		WHERE empire_id = 1
-			AND resource_name_id = _get_string( 'test1' );
-	SELECT diag_test_name( 'defs.uoc_resource() - new resources are added to empires' );
-	SELECT defs.uoc_resource( 'test3' , 'test4' , 'test5' , 1 );
-	SELECT is( COUNT(*)::INT , 1 )
-		FROM emp.resources
-		WHERE empire_id = 1
-			AND resource_name_id = _get_string( 'test3' );
-
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql
index 79f5a3b..f9a67a4 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/030-uoc-natres-internal.sql
@@ -10,7 +10,7 @@ BEGIN;
 	SELECT _create_resources( 1 , 'basicRes' );
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 38 );
+	SELECT plan( 39 );
 
 	SELECT diag_test_name( 'defs.uoc_natres_internal() - creation without category' );
 	SELECT is( defs.uoc_natres_internal( 'test3' , 'test4' , NULL , 1 ,
@@ -171,21 +171,39 @@ BEGIN;
 	SELECT is( defs.uoc_natres_internal( 'test5' , 'test2' , NULL , 1 ,
 		0.5 , 100 , 50 , 0.5 , 0.05 , 0.75 , 0.5 ) , 'BAD_VALUE' );
 
-	/* Reset resources, create empire */
+	/* Reset resources, create temporary table, replace
+	 * defs.add_resource_records() to make sure it's called when needed (and
+	 * only then)
+	 */
 	DELETE FROM defs.natural_resources;
 	DELETE FROM defs.resources;
+	CREATE TEMPORARY TABLE arr_called( on_id INT ) ON COMMIT DROP;
+	CREATE OR REPLACE FUNCTION defs.add_resource_records( _resource INT )
+		RETURNS VOID
+		STRICT VOLATILE
+		SECURITY INVOKER
+	AS $$
+		INSERT INTO arr_called VALUES( $1 );
+	$$ LANGUAGE SQL;
 	ALTER TABLE emp.empires
 		DROP CONSTRAINT fk_empires_name;
 	INSERT INTO emp.empires ( name_id , cash )
 		VALUES ( 1 , 0 );
 
-	SELECT diag_test_name( 'defs.uoc_natres_internal() - new resources are added to empires' );
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - defs.add_resource_record() called on new resource' );
 	SELECT defs.uoc_natres_internal( 'test1' , 'test2' , NULL , 1 ,
 		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 );
 	SELECT is( COUNT(*)::INT , 1 )
-		FROM emp.resources
-		WHERE empire_id = 1
-			AND resource_name_id = _get_string( 'test1' );
+		FROM arr_called
+		WHERE on_id = _get_string( 'test1' );
+	DELETE FROM arr_called;
+
+	SELECT diag_test_name( 'defs.uoc_natres_internal() - defs.add_resource_record() not called on resource update' );
+	SELECT defs.uoc_natres_internal( 'test1' , 'test3' , NULL , 1 ,
+		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 );
+	SELECT is( COUNT(*)::INT , 0 )
+		FROM arr_called
+		WHERE on_id = _get_string( 'test1' );
 
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql
index 87889fe..b27130c 100644
--- a/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql
+++ b/legacyworlds-server-data/db-structure/tests/admin/040-functions/025-resources/040-uoc-natural-resource.sql
@@ -2,264 +2,65 @@
  * Test the defs.uoc_natural_resource() functions
  */
 BEGIN;
-	
-	/* We need a few strings to be used when creating resource definitions, and a basic resource. */
-	\i utils/strings.sql
-	\i utils/resources.sql
-	SELECT _create_test_strings( 7 );
-	SELECT _create_resources( 1 , 'basicRes' );
+	/* Create a fake internal function that will just push its parameters to
+	 * a temporary table.
+	 */
+	CREATE TEMPORARY TABLE calls_to_internal(
+		name TEXT ,
+		description TEXT ,
+		category TEXT ,
+		weight INT ,
+		presence DOUBLE PRECISION ,
+		quantity_avg DOUBLE PRECISION ,
+		quantity_dev DOUBLE PRECISION ,
+		difficulty_avg DOUBLE PRECISION ,
+		difficulty_dev DOUBLE PRECISION ,
+		recovery_avg DOUBLE PRECISION ,
+		recovery_dev DOUBLE PRECISION
+	) ON COMMIT DROP;
+	CREATE OR REPLACE FUNCTION defs.uoc_natres_internal(
+				_name TEXT ,
+				_description TEXT ,
+				_category TEXT ,
+				_weight INT ,
+				_presence DOUBLE PRECISION ,
+				_quantity_avg DOUBLE PRECISION ,
+				_quantity_dev DOUBLE PRECISION ,
+				_difficulty_avg DOUBLE PRECISION ,
+				_difficulty_dev DOUBLE PRECISION ,
+				_recovery_avg DOUBLE PRECISION ,
+				_recovery_dev DOUBLE PRECISION )
+		RETURNS defs.resource_update_result
+		CALLED ON NULL INPUT
+		VOLATILE
+		SECURITY INVOKER
+	AS $$
+		INSERT INTO calls_to_internal VALUES (
+			$1 , $2 , CASE WHEN $3 IS NULL THEN 'NULL' ELSE $3 END , $4 ,
+			$5 , $6 , $7 , $8 , $9 , $10 , $11
+		);
+		SELECT 'BAD_STRINGS'::defs.resource_update_result;
+	$$ LANGUAGE SQL;
+
 
 	/****** TESTS BEGIN HERE ******/
-	SELECT plan( 60 );
+	SELECT plan( 2 );
 
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL name (no category)' );
-	SELECT is( defs.uoc_natural_resource( NULL , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL description (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , NULL , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL weight (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , NULL ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL presence (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		NULL , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average quantity (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , NULL , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL quantity deviation (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , 100 , NULL , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average difficulty (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , 100 , 50 , NULL , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL difficulty deviation (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , NULL , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average recovery (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , NULL , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL recovery deviation (no category)' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , NULL ) , NULL );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL name' );
-	SELECT is( defs.uoc_natural_resource( NULL , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL description' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , NULL , 'test5' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL category' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , NULL , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL weight' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , NULL ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL presence' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		NULL , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average quantity' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		0.5 , NULL , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL quantity deviation' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , NULL , 0.5 , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average difficulty' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , 50 , NULL , 0.05 , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL difficulty deviation' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , 50 , 0.5 , NULL , 0.5 , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL average recovery' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , NULL , 0.05 ) , NULL );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - NULL recovery deviation' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , NULL ) , NULL );
-
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation without category' );
-	SELECT is( defs.uoc_natural_resource( 'test3' , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'CREATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation results without category - basic' );
-	SELECT results_eq(
-		$$ SELECT resource_name_id , resource_description_id , resource_weight
-				FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test3' )
-					AND resource_category_id IS NULL $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , 1 ) $$
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - Calls internal function (without category)' );
+	SELECT defs.uoc_natural_resource( 'a' , 'b' , 1 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 , 0.6 , 0.7 );
+	SELECT set_eq(
+		$$ SELECT * FROM calls_to_internal; $$ ,
+		$$ VALUES( 'a' , 'b' , 'NULL' , 1 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 , 0.6 , 0.7 ) $$
 	);
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation results without category - nat. res.' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.natural_resources
-				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , 0.5::DOUBLE PRECISION , 100::DOUBLE PRECISION ,
-			50::DOUBLE PRECISION , 0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ,
-			0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ) $$
+	DELETE FROM calls_to_internal;
+
+	SELECT diag_test_name( 'defs.uoc_natural_resource() - Calls internal function (with category)' );
+	SELECT defs.uoc_natural_resource( 'a' , 'b' , 'c' , 1 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 , 0.6 , 0.7 );
+	SELECT set_eq(
+		$$ SELECT * FROM calls_to_internal; $$ ,
+		$$ VALUES( 'a' , 'b' , 'c' , 1 , 0.1 , 0.2 , 0.3 , 0.4 , 0.5 , 0.6 , 0.7 ) $$
 	);
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation with category' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test6' , 'test7' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'CREATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation results with category - basic' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test5' ) $$ ,
-		$$ VALUES ( _get_string( 'test5' ) , _get_string( 'test6' ) , _get_string( 'test7' ) , 1 ) $$
-	);
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - creation results with category - nat. res.' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.natural_resources
-				WHERE resource_name_id = _get_string( 'test5' ) $$ ,
-		$$ VALUES ( _get_string( 'test5' ) , 0.5::DOUBLE PRECISION , 100::DOUBLE PRECISION ,
-			50::DOUBLE PRECISION , 0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ,
-			0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ) $$
-	);
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update without category' );
-	SELECT is( defs.uoc_natural_resource( 'test3' , 'test7' , 2 ,
-		0.3 , 300 , 30 , 0.3 , 0.03 , 0.3 , 0.03 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update results without category - basic' );
-	SELECT results_eq(
-		$$ SELECT resource_name_id , resource_description_id , resource_weight
-				FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test3' )
-					AND resource_category_id IS NULL $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test7' ) , 2 ) $$
-	);
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update results without category - nat. res.' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.natural_resources
-				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , 0.3::DOUBLE PRECISION , 300::DOUBLE PRECISION ,
-			30::DOUBLE PRECISION , 0.3::DOUBLE PRECISION , 0.03::DOUBLE PRECISION ,
-			0.3::DOUBLE PRECISION , 0.03::DOUBLE PRECISION ) $$
-	);
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update with category' );
-	SELECT is( defs.uoc_natural_resource( 'test3' , 'test4' , 'test7' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update results with category' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.resources
-				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , _get_string( 'test4' ) , _get_string( 'test7' ) , 1 ) $$
-	);
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update results with category - nat. res.' );
-	SELECT results_eq(
-		$$ SELECT * FROM defs.natural_resources
-				WHERE resource_name_id = _get_string( 'test3' ) $$ ,
-		$$ VALUES ( _get_string( 'test3' ) , 0.5::DOUBLE PRECISION , 100::DOUBLE PRECISION ,
-			50::DOUBLE PRECISION , 0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ,
-			0.5::DOUBLE PRECISION , 0.05::DOUBLE PRECISION ) $$
-	);
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - incorrect name string' );
-	SELECT is( defs.uoc_natural_resource( 'does-not-exist' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_STRINGS' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - incorrect description string' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'does-not-exist' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_STRINGS' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - incorrect category string' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test2' , 'does-not-exist' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_STRINGS' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - duplicate description on new resource' );
-	SELECT is( defs.uoc_natural_resource( 'test1' , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'DUP_DESCR' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - duplicate description on existing resource' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test4' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'DUP_DESCR' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - update on basic resource' );
-	SELECT is( defs.uoc_natural_resource( 'basicRes1' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_TYPE' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - weight <= 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 0 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - P(presence) <= 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - P(presence) >= 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - max. quantity <= 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 0 , 50 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - quantity dev. < 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , -1 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - quantity max. - dev. <= 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 100 , 0.5 , 0.05 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. = 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0 , 0 , 0.5 , 0.05 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. < 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , -0.00001 , 0 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. = 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 1 , 0 , 0.5 , 0.05 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. > 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 1.00001 , 0 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty dev. < 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , -1 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. - dev. < 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.25 , 0.5 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - difficulty max. + dev. > 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.75 , 0.5 , 0.5 , 0.05 ) , 'BAD_VALUE' );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. <= 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0 , 0 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. = 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 1 , 0 ) , 'UPDATED' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. > 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 1.0001 , 0 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery dev. < 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , -0.25 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. - dev. <= 0' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.25 , 0.25 ) , 'BAD_VALUE' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - recovery max. + dev. > 1' );
-	SELECT is( defs.uoc_natural_resource( 'test5' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.75 , 0.5 ) , 'BAD_VALUE' );
-
-	/* Reset resources, create empire */
-	DELETE FROM defs.natural_resources;
-	DELETE FROM defs.resources;
-	ALTER TABLE emp.empires
-		DROP CONSTRAINT fk_empires_name;
-	INSERT INTO emp.empires ( name_id , cash )
-		VALUES ( 1 , 0 );
-
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - new resources are added to empires (no category)' );
-	SELECT defs.uoc_natural_resource( 'test1' , 'test2' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 );
-	SELECT is( COUNT(*)::INT , 1 )
-		FROM emp.resources
-		WHERE empire_id = 1
-			AND resource_name_id = _get_string( 'test1' );
-	SELECT diag_test_name( 'defs.uoc_natural_resource() - new resources are added to empires' );
-	SELECT defs.uoc_natural_resource( 'test3' , 'test4' , 'test5' , 1 ,
-		0.5 , 100 , 50 , 0.5 , 0.05 , 0.5 , 0.05 );
-	SELECT is( COUNT(*)::INT , 1 )
-		FROM emp.resources
-		WHERE empire_id = 1
-			AND resource_name_id = _get_string( 'test3' );
+	DELETE FROM calls_to_internal;
 
 	SELECT * FROM finish( );
 ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/030-data/100-universe/020-verse-planet-resources.sql b/legacyworlds-server-data/db-structure/tests/user/030-data/100-universe/020-verse-planet-resources.sql
new file mode 100644
index 0000000..00fa1e4
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/030-data/100-universe/020-verse-planet-resources.sql
@@ -0,0 +1,28 @@
+/*
+ * Test privileges on verse.planet_resources
+ */
+BEGIN;
+	SELECT plan( 4 );
+	
+	SELECT diag_test_name( 'verse.planet_resources - INSERT privileges' );
+	SELECT throws_ok(
+		$$ INSERT INTO verse.planet_resources( resource_name_id ) VALUES ( 1 ); $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'verse.resource_providers - UPDATE privileges' );
+	SELECT throws_ok(
+		$$ UPDATE verse.planet_resources SET pres_income = 42; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'verse.planet_resources - SELECT privileges' );
+	SELECT throws_ok(
+		$$ SELECT * FROM verse.planet_resources; $$ ,
+		42501 );
+
+	SELECT diag_test_name( 'verse.planet_resources - DELETE privileges' );
+	SELECT throws_ok(
+		$$ DELETE FROM verse.planet_resources; $$ ,
+		42501 );
+
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file
diff --git a/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/005-add-resource-records.sql b/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/005-add-resource-records.sql
new file mode 100644
index 0000000..ee0a892
--- /dev/null
+++ b/legacyworlds-server-data/db-structure/tests/user/040-functions/025-resources/005-add-resource-records.sql
@@ -0,0 +1,14 @@
+/*
+ * Test privileges on defs.add_resource_records()
+ */
+BEGIN;
+
+	SELECT plan( 1 );
+	
+	SELECT diag_test_name( 'defs.add_resource_records() - Privileges' );
+	PREPARE _test_this AS
+		SELECT defs.add_resource_records( 1 );
+	SELECT throws_ok( '_test_this' , 42501 );
+	
+	SELECT * FROM finish( );
+ROLLBACK;
\ No newline at end of file