/**
* Copyright (C) 2009-2013 FoundationDB, LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.foundationdb.sql.pg;
import com.foundationdb.ais.model.Parameter;
import com.foundationdb.ais.model.Routine;
import com.foundationdb.server.Quote;
import com.foundationdb.server.error.ExternalRoutineInvocationException;
import com.foundationdb.server.error.SQLParserInternalException;
import com.foundationdb.server.types.FormatOptions;
import com.foundationdb.server.types.value.ValueSource;
import com.foundationdb.sql.StandardException;
import com.foundationdb.sql.server.ServerJavaRoutine;
import com.foundationdb.sql.server.ServerJavaValues;
import com.foundationdb.sql.types.DataTypeDescriptor;
import com.foundationdb.sql.types.TypeId;
import com.foundationdb.util.AkibanAppender;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.List;
import java.util.Queue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class PostgresJavaRoutineJsonOutputter extends PostgresOutputter<ServerJavaRoutine>
{
private Queue<ResultSet> resultSets;
public PostgresJavaRoutineJsonOutputter(PostgresQueryContext context,
PostgresJavaRoutine statement,
Queue<ResultSet> resultSets) {
super(context, statement);
this.resultSets = resultSets;
}
@Override
public void beforeData() throws IOException {
if (context.getServer().getOutputFormat() == PostgresServerSession.OutputFormat.JSON_WITH_META_DATA) {
outputMetaData();
}
}
@Override
public void output(ServerJavaRoutine javaRoutine) throws IOException {
messenger.beginMessage(PostgresMessages.DATA_ROW_TYPE.code());
messenger.writeShort(1);
encoder.reset();
outputResults(javaRoutine, encoder.getAppender());
ByteArrayOutputStream bytes = encoder.getByteStream();
messenger.writeInt(bytes.size());
messenger.writeByteStream(bytes);
messenger.sendMessage();
}
protected void outputResults(ServerJavaRoutine javaRoutine, AkibanAppender appender) throws IOException {
encoder.appendString("{");
boolean first = true;
Routine routine = javaRoutine.getInvocation().getRoutine();
List<Parameter> params = routine.getParameters();
for (int i = 0; i < params.size(); i++) {
Parameter param = params.get(i);
if (param.getDirection() == Parameter.Direction.IN) continue;
String name = param.getName();
if (name == null)
name = String.format("arg%d", i+1);
Object value = javaRoutine.getOutParameter(param, i);
PostgresType pgType = PostgresType.fromAIS(param);
outputValue(name, value, pgType, appender, first);
first = false;
}
if (routine.getReturnValue() != null) {
Object value = javaRoutine.getOutParameter(routine.getReturnValue(),
ServerJavaValues.RETURN_VALUE_INDEX);
PostgresType pgType = PostgresType.fromAIS(routine.getReturnValue());
outputValue("return", value, pgType, appender, first);
first = false;
}
int nresults = 0;
while (!resultSets.isEmpty()) {
String name = (nresults++ > 0) ? String.format("result_set_%d", nresults) : "result_set";
outputValue(name, resultSets.remove(), null, appender, first);
first = false;
}
encoder.appendString("}");
}
protected void outputValue(String name, Object value, PostgresType pgType,
AkibanAppender appender, boolean first)
throws IOException {
encoder.appendString(first ? "\"" : ",\"");
Quote.DOUBLE_QUOTE.append(appender, name);
encoder.appendString("\":");
if (value == null) {
encoder.appendString("null");
}
else if (value instanceof ResultSet) {
try {
outputResultSet((ResultSet)value, appender);
}
catch (SQLException ex) {
throw new ExternalRoutineInvocationException(((PostgresJavaRoutine)statement).getInvocation().getRoutineName(), ex);
}
}
else {
ValueSource source = encoder.valuefromObject(value, pgType);
FormatOptions options = context.getServer().getFormatOptions();
pgType.getType().formatAsJson(source, appender, options);
}
}
protected void outputResultSet(ResultSet resultSet, AkibanAppender appender) throws IOException, SQLException {
ResultSetMetaData metaData = resultSet.getMetaData();
int ncols = metaData.getColumnCount();
PostgresType[] pgTypes = new PostgresType[ncols];
for (int i = 0; i < ncols; i++) {
DataTypeDescriptor sqlType = resultColumnSQLType(metaData, i+1);
pgTypes[i] = PostgresType.fromDerby(sqlType, null);
}
encoder.appendString("[");
boolean first = true;
while (resultSet.next()) {
encoder.appendString(first ? "{" : ",{");
for (int i = 0; i < ncols; i++) {
String name = metaData.getColumnName(i+1);
Object value = resultSet.getObject(i+1);
outputValue(name, value, pgTypes[i], appender, (i == 0));
}
encoder.appendString("}");
first = false;
}
resultSet.close();
encoder.appendString("]");
}
public void outputMetaData() throws IOException {
messenger.beginMessage(PostgresMessages.DATA_ROW_TYPE.code());
messenger.writeShort(1);
encoder.reset();
try {
outputMetaData(encoder.getAppender());
}
catch (SQLException ex) {
throw new ExternalRoutineInvocationException(((PostgresJavaRoutine)statement).getInvocation().getRoutineName(), ex);
}
ByteArrayOutputStream bytes = encoder.getByteStream();
messenger.writeInt(bytes.size());
messenger.writeByteStream(bytes);
messenger.sendMessage();
}
protected void outputMetaData(AkibanAppender appender) throws IOException, SQLException {
encoder.appendString("[");
boolean first = true;
Routine routine = ((PostgresJavaRoutine)statement).getInvocation().getRoutine();
List<Parameter> params = routine.getParameters();
for (int i = 0; i < params.size(); i++) {
Parameter param = params.get(i);
if (param.getDirection() == Parameter.Direction.IN) continue;
String name = param.getName();
if (name == null)
name = String.format("arg%d", i+1);
outputParameterMetaData(name, param, appender, first);
first = false;
}
if (routine.getReturnValue() != null) {
outputParameterMetaData("return", routine.getReturnValue(), appender, first);
first = false;
}
int i = 0;
for (ResultSet resultSet : resultSets) {
i++;
outputResultSetMetaData(String.format("result%d", i),
resultSet.getMetaData(), appender, (i == 1));
}
encoder.appendString("]");
}
protected void outputParameterMetaData(String name, Parameter param,
AkibanAppender appender, boolean first)
throws IOException {
PostgresType pgType = PostgresType.fromAIS(param);
if (!first)
encoder.appendString(",");
encoder.appendString("{\"name\":\"");
Quote.DOUBLE_QUOTE.append(appender, name);
encoder.appendString("\"");
encoder.appendString(",\"oid\":");
encoder.getWriter().print(pgType.getOid());
encoder.appendString(",\"type\":\"");
Quote.DOUBLE_QUOTE.append(appender, param.getTypeDescription());
encoder.appendString("\"");
if (pgType.getModifier() > 0) {
int mod = pgType.getModifier() - 4;
if (pgType.getOid() == PostgresType.TypeOid.NUMERIC_TYPE_OID.getOid()) {
encoder.appendString(",\"precision\":");
encoder.getWriter().print(mod >> 16);
encoder.appendString(",\"scale\":");
encoder.getWriter().print(mod & 0xFFFF);
}
else {
encoder.appendString(",\"length\":");
encoder.getWriter().print(mod);
}
}
encoder.appendString("}");
}
protected void outputResultSetMetaData(String name, ResultSetMetaData metaData,
AkibanAppender appender, boolean first)
throws IOException, SQLException {
if (!first)
encoder.appendString(",");
encoder.appendString("{\"name\":\"");
Quote.DOUBLE_QUOTE.append(appender, name);
encoder.appendString("\",columns:[");
int ncols = metaData.getColumnCount();
for (int i = 0; i < ncols; i++) {
DataTypeDescriptor sqlType = resultColumnSQLType(metaData, i+1);
PostgresType pgType = PostgresType.fromDerby(sqlType, null);
if (i > 0)
encoder.appendString(",");
encoder.appendString("{\"name\":\"");
Quote.DOUBLE_QUOTE.append(appender, metaData.getColumnName(i+1));
encoder.appendString("\"");
encoder.appendString(",\"oid\":");
encoder.getWriter().print(pgType.getOid());
encoder.appendString(",\"type\":\"");
Quote.DOUBLE_QUOTE.append(appender, sqlType.toString());
encoder.appendString("\"");
if (sqlType.getTypeId().isDecimalTypeId()) {
encoder.appendString(",\"precision\":");
encoder.getWriter().print(sqlType.getPrecision());
encoder.appendString(",\"scale\":");
encoder.getWriter().print(sqlType.getScale());
}
else if (sqlType.getTypeId().variableLength()) {
encoder.appendString(",\"length\":");
encoder.getWriter().print(sqlType.getMaximumWidth());
}
encoder.appendString("}");
}
encoder.appendString("]}");
}
protected DataTypeDescriptor resultColumnSQLType(ResultSetMetaData metaData, int i)
throws SQLException {
TypeId typeId = TypeId.getBuiltInTypeId(metaData.getColumnType(i));
if (typeId == null) {
try {
typeId = TypeId.getUserDefinedTypeId(metaData.getColumnTypeName(i),
false);
}
catch (StandardException ex) {
throw new SQLParserInternalException(ex);
}
}
if (typeId.isDecimalTypeId()) {
return new DataTypeDescriptor(typeId,
metaData.getPrecision(i),
metaData.getScale(i),
metaData.isNullable(i) != ResultSetMetaData.columnNoNulls,
metaData.getColumnDisplaySize(i));
}
else {
return new DataTypeDescriptor(typeId,
metaData.isNullable(i) != ResultSetMetaData.columnNoNulls,
metaData.getColumnDisplaySize(i));
}
}
}