/*
* Copyright (c) 2015 Tada AB and other contributors, as listed below.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the The BSD 3-Clause License
* which accompanies this distribution, and is available at
* http://opensource.org/licenses/BSD-3-Clause
*
* Contributors:
* Chapman Flack
*/
package org.postgresql.pljava.example.annotation;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.postgresql.pljava.annotation.SQLAction;
import org.postgresql.pljava.annotation.SQLActions;
import org.postgresql.pljava.annotation.Function;
/**
* Test that strings containing characters from all Unicode planes
* are passed between PG and Java without alteration (issue 21).
* <p>
* This function takes a string and an array of ints constructed in PG, such
* that PG believes the codepoints in the string to correspond exactly with the
* ints in the array. The function compares the two, generates a new array from
* the codepoints Java sees in the string and a new Java string from the
* original array, and returns a tuple (matched, cparray, s) where
* {@code matched} indicates whether the original array and string matched
* as seen by Java, and {@code cparray} and {@code s} are the new array and
* string generated in Java.
* <p>
* The supplied test query generates all Unicode code points 1k at a time,
* calls this function on each (1k array, 1k string) pair, and counts a failure
* if {@code matched} is false or the original and returned arrays or strings
* do not match as seen in SQL.
*/
@SQLActions({
@SQLAction(provides="postgresql_unicodetest", install=
" select case " +
" when 90000 <= cast(current_setting('server_version_num') as integer) " +
" and 'UTF8' = current_setting('server_encoding') " +
" then set_config('pljava.implementors', 'postgresql_unicodetest,' || " +
" current_setting('pljava.implementors'), true) " +
" end"
),
@SQLAction(requires="unicodetest fn",
implementor="postgresql_unicodetest",
install=
" with " +
" usable_codepoints ( cp ) as ( " +
" select generate_series(1,x'd7ff'::int) " +
" union all " +
" select generate_series(x'e000'::int,x'10ffff'::int) " +
" ), " +
" test_inputs ( groupnum, cparray, s ) as ( " +
" select " +
" cp / 1024 as groupnum, " +
" array_agg(cp order by cp), string_agg(chr(cp), '' order by cp) " +
" from usable_codepoints " +
" group by groupnum " +
" ), " +
" test_outputs as ( " +
" select groupnum, cparray, s, unicodetest(s, cparray) as roundtrip " +
" from test_inputs " +
" ), " +
" test_failures as ( " +
" select * " +
" from test_outputs " +
" where " +
" cparray != (roundtrip).cparray or s != (roundtrip).s " +
" or not (roundtrip).matched " +
" ), " +
" test_summary ( n_failing_groups, first_failing_group ) as ( " +
" select count(*), min(groupnum) from test_failures " +
" ) " +
" select " +
" case when n_failing_groups > 0 then " +
" javatest.logmessage('WARNING', format( " +
" '%s 1k codepoint ranges had mismatches, first is block starting 0x%s', " +
" n_failing_groups, to_hex(1024 * first_failing_group))) " +
" else " +
" javatest.logmessage('INFO', " +
" 'all Unicode codepoint ranges roundtripped successfully.') " +
" end " +
" from test_summary"
),
@SQLAction(
install=
"CREATE TYPE unicodetestrow AS " +
"(matched boolean, cparray integer[], s text)",
remove="DROP TYPE unicodetestrow",
provides="unicodetestrow type"
)
})
public class UnicodeRoundTripTest {
/**
* This function takes a string and an array of ints constructed in PG,
* such that PG believes the codepoints in the string to correspond exactly
* with the ints in the array. The function compares the two, generates a
* new array from the codepoints Java sees in the string and a new Java
* string from the original array, and returns a tuple (matched, cparray,
* s) where {@code matched} indicates whether the original array and string
* matched as seen by Java, and {@code cparray} and {@code s} are the new
* array and string generated in Java.
*
* @param s A string, whose codepoints should match the entries of
* {@code ints}
* @param ints Array of ints that should match the codepoints in {@code s}
* @param rs OUT (matched, cparray, s) as described above
* @return true to indicate the OUT tuple is not null
*/
@Function(type="unicodetestrow",
requires="unicodetestrow type", provides="unicodetest fn")
public static boolean unicodetest(String s, int[] ints, ResultSet rs)
throws SQLException {
boolean ok = true;
int cpc = s.codePointCount(0, s.length());
Integer[] myInts = new Integer[cpc];
int ci = 0;
for ( int cpi = 0; cpi < cpc; cpi++ ) {
myInts[cpi] = s.codePointAt(ci);
ci = s.offsetByCodePoints(ci, 1);
}
String myS = new String(ints, 0, ints.length);
if ( ints.length != myInts.length )
ok = false;
else
for ( int i = 0; i < ints.length; ++ i )
if ( ints[i] != myInts[i] )
ok = false;
rs.updateBoolean("matched", ok);
rs.updateObject("cparray", myInts);
rs.updateString("s", myS);
return true;
}
}